// BrokerCheck visible-range tick-volume profile.
// Tick activity allocated uniformly across each bar's high-low range.
// This approximation is not traded volume at individual prices.
#property copyright "BrokerCheck"
#property version "1.00"
#property indicator_chart_window
#property indicator_buffers 3
#property indicator_plots 3
#property indicator_type1 DRAW_NONE
#property indicator_type2 DRAW_NONE
#property indicator_type3 DRAW_NONE
#property indicator_label1 "POC"
#property indicator_label2 "VAL"
#property indicator_label3 "VAH"
input int Bins=20;
input double ValueAreaPercent=70.0;
input bool IncludeFormingBar=false;
double PocBuffer[],ValBuffer[],VahBuffer[];
string objectPrefix;

//=== PROFILE CORE BEGIN
bool ProfileCore(const double &low[],const double &high[],const double &vol[],const int n,
                 const int bins,const double percent,double &edges[],double &mass[],
                 double &poc,double &val,double &vah,int &peak,int &left,int &right)
{
  poc=val=vah=EMPTY_VALUE; peak=left=right=-1;
  ArrayResize(edges,0); ArrayResize(mass,0);
  if(n<1 || bins<1 || bins>500 || percent<=0 || percent>100 || !MathIsValidNumber(percent) ||
     ArraySize(low)<n || ArraySize(high)<n || ArraySize(vol)<n) return false;
  double bottom=low[0],top=high[0],total=0;
  for(int i=0;i<n;i++)
  {
    if(!MathIsValidNumber(low[i]) || !MathIsValidNumber(high[i]) || !MathIsValidNumber(vol[i]) || high[i]<low[i] || vol[i]<0) return false;
    bottom=MathMin(bottom,low[i]); top=MathMax(top,high[i]); total+=vol[i];
  }
  const int count=(top==bottom ? 1 : bins);
  const double width=(top-bottom)/count;
  ArrayResize(edges,count+1); ArrayResize(mass,count); ArrayInitialize(mass,0);
  for(int j=0;j<=count;j++) edges[j]=bottom+j*width;
  edges[count]=top;
  for(int i=0;i<n;i++)
  {
    if(high[i]==low[i])
    {
      int j=0;
      while(j<count-1 && low[i]>=edges[j+1]) j++;
      mass[j]+=vol[i];
    }
    else for(int j=0;j<count;j++)
    {
      double overlap=MathMax(0,MathMin(high[i],edges[j+1])-MathMax(low[i],edges[j]));
      mass[j]+=vol[i]*(overlap/(high[i]-low[i]));
    }
  }
  if(total==0) return true;
  peak=0;
  for(int j=1;j<count;j++) if(mass[j]>mass[peak]) peak=j;
  left=right=peak;
  double included=mass[peak],target=total*percent/100;
  while(included<target && (left>0 || right<count-1))
  {
    double down=(left>0 ? mass[left-1] : -1),up=(right<count-1 ? mass[right+1] : -1);
    if(down>=up) { left--; included+=mass[left]; }
    else { right++; included+=mass[right]; }
  }
  poc=(edges[peak]+edges[peak+1])/2; val=edges[left]; vah=edges[right+1];
  return true;
}
//=== PROFILE CORE END

int OnInit()
{
  if(Bins<1 || Bins>500 || ValueAreaPercent<=0 || ValueAreaPercent>100) return INIT_PARAMETERS_INCORRECT;
  SetIndexBuffer(0,PocBuffer,INDICATOR_DATA); SetIndexBuffer(1,ValBuffer,INDICATOR_DATA); SetIndexBuffer(2,VahBuffer,INDICATOR_DATA);
  ArraySetAsSeries(PocBuffer,true); ArraySetAsSeries(ValBuffer,true); ArraySetAsSeries(VahBuffer,true);
  objectPrefix="BCVP_"+IntegerToString(ChartID())+"_"+IntegerToString((long)GetMicrosecondCount())+"_";
  IndicatorSetString(INDICATOR_SHORTNAME,"BrokerCheck visible tick-volume profile");
  return INIT_SUCCEEDED;
}
void Level(const string name,const datetime first,const datetime last,const double price,const color ink)
{
  ObjectCreate(0,objectPrefix+name,OBJ_TREND,0,first,price,last,price);
  ObjectSetInteger(0,objectPrefix+name,OBJPROP_COLOR,ink);
  ObjectSetInteger(0,objectPrefix+name,OBJPROP_RAY_RIGHT,false);
  ObjectSetInteger(0,objectPrefix+name,OBJPROP_SELECTABLE,false);
  ObjectSetString(0,objectPrefix+name,OBJPROP_TOOLTIP,name+" "+DoubleToString(price,_Digits));
}
void RefreshProfile()
{
  ArrayInitialize(PocBuffer,EMPTY_VALUE); ArrayInitialize(ValBuffer,EMPTY_VALUE); ArrayInitialize(VahBuffer,EMPTY_VALUE);
  ObjectsDeleteAll(0,objectPrefix);
  int oldest=(int)ChartGetInteger(0,CHART_FIRST_VISIBLE_BAR);
  int visible=(int)ChartGetInteger(0,CHART_VISIBLE_BARS);
  if(visible<1 || oldest<0) return;
  int newest=(int)MathMax(IncludeFormingBar ? 0 : 1,oldest-visible+1);
  oldest=(int)MathMin(oldest,Bars(_Symbol,_Period)-1);
  if(oldest<newest) return;
  int requested=oldest-newest+1;
  MqlRates rates[];
  int n=CopyRates(_Symbol,_Period,newest,requested,rates);
  // Incomplete history must not silently change the selected window.
  if(n!=requested) return;
  double lows[],highs[],vols[],edges[],mass[],poc,val,vah;
  ArrayResize(lows,n); ArrayResize(highs,n); ArrayResize(vols,n);
  for(int i=0;i<n;i++) { lows[i]=rates[i].low; highs[i]=rates[i].high; vols[i]=(double)rates[i].tick_volume; }
  int peak,left,right;
  if(!ProfileCore(lows,highs,vols,n,Bins,ValueAreaPercent,edges,mass,poc,val,vah,peak,left,right) || peak<0) return;
  for(int shift=newest;shift<=oldest && shift<ArraySize(PocBuffer);shift++)
  { PocBuffer[shift]=poc; ValBuffer[shift]=val; VahBuffer[shift]=vah; }
  datetime first=rates[0].time,last=rates[n-1].time+(datetime)PeriodSeconds();
  for(int j=0;j<ArraySize(mass);j++)
  {
    datetime end=first+(datetime)((last-first)*0.30*mass[j]/mass[peak]);
    string name=objectPrefix+"bin_"+IntegerToString(j);
    ObjectCreate(0,name,OBJ_RECTANGLE,0,first,edges[j],end,edges[j+1]);
    ObjectSetInteger(0,name,OBJPROP_COLOR,j>=left && j<=right ? clrSteelBlue : clrSlateGray);
    ObjectSetInteger(0,name,OBJPROP_FILL,true); ObjectSetInteger(0,name,OBJPROP_BACK,true);
    ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
    ObjectSetString(0,name,OBJPROP_TOOLTIP,"Allocated tick volume "+DoubleToString(mass[j],2));
  }
  Level("POC",first,last,poc,clrOrange); Level("VAL",first,last,val,clrDeepSkyBlue); Level("VAH",first,last,vah,clrDeepSkyBlue);
}
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[])
{
  RefreshProfile();
  return rates_total;
}
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
{
  if(id==CHARTEVENT_CHART_CHANGE) { RefreshProfile(); ChartRedraw(); }
}
void OnDeinit(const int reason) { ObjectsDeleteAll(0,objectPrefix); }
