mql5/Renko500_Separate.mq5
277 行
10 KiB
MQL5
Aleksei Rytikov
2026-09-02 19:41:12 +00:00
#property strict
#property indicator_separate_window
#property indicator_plots 0
#property indicator_buffers 0
input int BoxSizePoints = 500;
input int InstanceID = 1;
input int MaxBricks = 3000;
input int BrickWidthPx = 10;
input int BrickGapPx = 1;
input int LeftMarginPx = 8;
input int RightMarginPx = 8;
input int TopMarginPx = 8;
input int BottomMarginPx = 8;
input datetime AnchorDateTime = D'2026.01.01 00:00';
struct RenkoBrick
{
double open;
double close;
int direction;
};
RenkoBrick Bricks[];
double BoxSize=0.0;
bool HasAnchor=false;
double Anchor=0.0;
double LastClose=0.0;
int LastDirection=0;
ulong LastTickMsc=0;
double LastTickBid=0.0;
int SubWindow=-1;
bool HistoryLoaded=false;
int HistoryAttempts=0;
datetime HistoryFrom=0;
datetime HistoryTo=0;
int LastCopiedTicks=-1;
int StableCopyCount=0;
string ObjectPrefix(){ return "RENKO500_I"+IntegerToString(InstanceID)+"_"; }
string IndicatorName(){ return "Renko 500 #"+IntegerToString(InstanceID); }
void AddBrick(const double open_price,const double close_price,const int direction)
{
int count=ArraySize(Bricks);
if(count>=MaxBricks)
{
for(int i=1;i<count;i++) Bricks[i-1]=Bricks[i];
count--;
ArrayResize(Bricks,count);
}
ArrayResize(Bricks,count+1);
Bricks[count].open=open_price;
Bricks[count].close=close_price;
Bricks[count].direction=direction;
}
void ProcessPrice(const double price)
{
if(price<=0.0) return;
if(!HasAnchor){ Anchor=price; HasAnchor=true; return; }
if(LastDirection==0)
{
if(price>=Anchor+BoxSize){ AddBrick(Anchor,Anchor+BoxSize,+1); LastClose=Anchor+BoxSize; LastDirection=+1; }
else if(price<=Anchor-BoxSize){ AddBrick(Anchor,Anchor-BoxSize,-1); LastClose=Anchor-BoxSize; LastDirection=-1; }
return;
}
if(LastDirection>0)
{
while(price>=LastClose+BoxSize){ AddBrick(LastClose,LastClose+BoxSize,+1); LastClose+=BoxSize; }
if(price<=LastClose-2.0*BoxSize)
{
AddBrick(LastClose-BoxSize,LastClose-2.0*BoxSize,-1);
LastClose-=2.0*BoxSize; LastDirection=-1;
while(price<=LastClose-BoxSize){ AddBrick(LastClose,LastClose-BoxSize,-1); LastClose-=BoxSize; }
}
return;
}
while(price<=LastClose-BoxSize){ AddBrick(LastClose,LastClose-BoxSize,-1); LastClose-=BoxSize; }
if(price>=LastClose+2.0*BoxSize)
{
AddBrick(LastClose+BoxSize,LastClose+2.0*BoxSize,+1);
LastClose+=2.0*BoxSize; LastDirection=+1;
while(price>=LastClose+BoxSize){ AddBrick(LastClose,LastClose+BoxSize,+1); LastClose+=BoxSize; }
}
}
void ProcessTick(const ulong time_msc,const double bid)
{
if(bid<=0.0) return;
if(time_msc==LastTickMsc && bid==LastTickBid) return;
LastTickMsc=time_msc; LastTickBid=bid; ProcessPrice(bid);
}
void DeleteObjects()
{
if(SubWindow<0) return;
string prefix=ObjectPrefix();
int total=ObjectsTotal(0,SubWindow,-1);
for(int i=total-1;i>=0;i--)
{
string name=ObjectName(0,i,SubWindow,-1);
if(StringFind(name,prefix)==0) ObjectDelete(0,name);
}
}
int GetEffectiveRightMarginPx(const int width)
{
int margin=RightMarginPx;
bool chart_shift=(bool)ChartGetInteger(0,CHART_SHIFT,0);
if(chart_shift)
{
double shift_size=ChartGetDouble(0,CHART_SHIFT_SIZE,0);
if(shift_size>0.0) margin+=(int)MathRound(width*shift_size/100.0);
}
return margin;
}
void DrawBrick(const int index,const int slot,const double min_price,const double max_price,const int height)
{
if(index<0 || index>=ArraySize(Bricks)) return;
if(height<=TopMarginPx+BottomMarginPx+5) return;
double top=MathMax(Bricks[index].open,Bricks[index].close);
double bottom=MathMin(Bricks[index].open,Bricks[index].close);
double range=max_price-min_price;
if(range<=0.0) return;
double usable=height-TopMarginPx-BottomMarginPx;
int y1=TopMarginPx+(int)MathRound((max_price-top)/range*usable);
int y2=TopMarginPx+(int)MathRound((max_price-bottom)/range*usable);
int brick_height=y2-y1; if(brick_height<2) brick_height=2;
int x=LeftMarginPx+slot*(BrickWidthPx+BrickGapPx);
string name=ObjectPrefix()+IntegerToString(index);
if(!ObjectCreate(0,name,OBJ_RECTANGLE_LABEL,SubWindow,0,0)) return;
ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_UPPER);
ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y1);
ObjectSetInteger(0,name,OBJPROP_XSIZE,BrickWidthPx);
ObjectSetInteger(0,name,OBJPROP_YSIZE,brick_height);
if(Bricks[index].direction>0){ ObjectSetInteger(0,name,OBJPROP_BGCOLOR,clrLimeGreen); ObjectSetInteger(0,name,OBJPROP_COLOR,clrGreen); }
else { ObjectSetInteger(0,name,OBJPROP_BGCOLOR,clrTomato); ObjectSetInteger(0,name,OBJPROP_COLOR,clrRed); }
ObjectSetInteger(0,name,OBJPROP_BORDER_TYPE,BORDER_FLAT);
ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
ObjectSetInteger(0,name,OBJPROP_SELECTED,false);
ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
ObjectSetInteger(0,name,OBJPROP_BACK,false);
}
void DrawRenko()
{
if(SubWindow<0) return;
int total=ArraySize(Bricks); if(total<=0) return;
int width=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS);
int height=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,SubWindow);
if(width<=0 || height<=0) return;
int step=BrickWidthPx+BrickGapPx; if(step<=0) return;
int effective_right_margin=GetEffectiveRightMarginPx(width);
int available_slots=(width-LeftMarginPx-effective_right_margin)/step;
if(available_slots<1) available_slots=1;
int visible=available_slots;
if(visible>total) visible=total;
int start=total-visible;
int slot_offset=available_slots-visible;
double min_price=DBL_MAX,max_price=-DBL_MAX;
for(int i=start;i<total;i++)
{
min_price=MathMin(min_price,MathMin(Bricks[i].open,Bricks[i].close));
max_price=MathMax(max_price,MathMax(Bricks[i].open,Bricks[i].close));
}
if(min_price==DBL_MAX || max_price==-DBL_MAX) return;
double range=max_price-min_price; if(range<=0.0) range=BoxSize;
double padding=range*0.05; min_price-=padding; max_price+=padding;
IndicatorSetDouble(INDICATOR_MINIMUM,min_price);
IndicatorSetDouble(INDICATOR_MAXIMUM,max_price);
DeleteObjects();
for(int i=start,slot=slot_offset;i<total;i++,slot++) DrawBrick(i,slot,min_price,max_price,height);
ChartRedraw(0);
}
void ResetRenkoState()
{
ArrayResize(Bricks,0); HasAnchor=false; Anchor=0.0; LastClose=0.0; LastDirection=0; LastTickMsc=0; LastTickBid=0.0;
}
bool PrepareHistoryRange()
{
if(AnchorDateTime<=0) return false;
datetime now=TimeCurrent(); if(now<=0) return false;
if(AnchorDateTime>now) return false;
HistoryFrom=AnchorDateTime;
HistoryTo=now;
return true;
}
bool LoadHistory()
{
if(HistoryFrom==0 || HistoryTo==0)
{
if(!PrepareHistoryRange()){ Print("Renko500 #",InstanceID,": invalid anchor date/time"); return false; }
}
MqlTick ticks[];
ResetLastError();
int copied=CopyTicksRange(_Symbol,ticks,COPY_TICKS_INFO,(ulong)HistoryFrom*1000,(ulong)HistoryTo*1000);
int error=GetLastError();
HistoryAttempts++;
if(copied<=0)
{
Print("Renko500 #",InstanceID,": history not ready. Attempt=",HistoryAttempts," Error=",error);
return false;
}
if(error!=0)
{
Print("Renko500 #",InstanceID,": history synchronization incomplete. Attempt=",HistoryAttempts," copied=",copied," Error=",error);
return false;
}
if(copied==LastCopiedTicks) StableCopyCount++; else StableCopyCount=0;
LastCopiedTicks=copied;
Print("Renko500 #",InstanceID,": CopyTicksRange attempt=",HistoryAttempts," copied=",copied," stable=",StableCopyCount);
if(StableCopyCount<2) return false;
ResetRenkoState();
int processed=0;
for(int i=0;i<copied;i++)
{
if(ticks[i].bid<=0.0) continue;
ProcessTick(ticks[i].time_msc,ticks[i].bid);
processed++;
}
LastTickMsc=ticks[copied-1].time_msc;
LastTickBid=ticks[copied-1].bid;
HistoryLoaded=true;
Print("Renko500 #",InstanceID,": HISTORICAL INITIALIZATION COMPLETE. AnchorDateTime=",TimeToString(AnchorDateTime,TIME_DATE|TIME_SECONDS)," Ticks=",copied," Processed=",processed," Bricks=",ArraySize(Bricks));
DrawRenko();
return true;
}
void ProcessCurrentTick()
{
if(!HistoryLoaded) return;
MqlTick tick; if(!SymbolInfoTick(_Symbol,tick)) return;
double bid=tick.bid; if(bid<=0.0) bid=tick.last; if(bid<=0.0) return;
ProcessTick(tick.time_msc,bid);
}
int OnInit()
{
if(InstanceID<1 || AnchorDateTime<=0 || AnchorDateTime>TimeCurrent()){ Print("Renko500: invalid parameters or AnchorDateTime"); return INIT_PARAMETERS_INCORRECT; }
BoxSize=BoxSizePoints*_Point;
if(BoxSize<=0.0){ Print("Renko500: invalid BoxSize"); return INIT_PARAMETERS_INCORRECT; }
IndicatorSetString(INDICATOR_SHORTNAME,IndicatorName());
ResetRenkoState(); HistoryLoaded=false; HistoryAttempts=0; HistoryFrom=0; HistoryTo=0; LastCopiedTicks=-1; StableCopyCount=0;
SubWindow=ChartWindowFind(); EventSetTimer(1);
Print("Renko500 Separate started. InstanceID=",InstanceID," SubWindow=",SubWindow," Box=",BoxSizePoints," points"," AnchorDateTime=",TimeToString(AnchorDateTime,TIME_DATE|TIME_SECONDS)," Prefix=",ObjectPrefix());
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason){ EventKillTimer(); if(SubWindow>=0) DeleteObjects(); ChartRedraw(0); }
void OnTimer()
{
if(SubWindow<0) SubWindow=ChartWindowFind(); if(SubWindow<0) return;
if(!HistoryLoaded){ LoadHistory(); return; }
ProcessCurrentTick(); DrawRenko();
}
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(SubWindow<0) SubWindow=ChartWindowFind();
if(!HistoryLoaded){ LoadHistory(); return rates_total; }
ProcessCurrentTick();
return rates_total;
}