| #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;
|
| }
|