//
//  "00-PriceBand" -- 
//
#property  copyright "00"
#property  link      "http://www.mql4.com/"

//---- indicator settings
#property  indicator_chart_window

#property  indicator_buffers  0

//---- defines

//---- indicator parameters
extern int    nBar             = 0;         // number of bars to count (0: all visible bars, N: latest N bars)
extern bool   bLeftOrigin      = false;     // draw bars from left to right
extern double priceStep        = 0.00;      // price margin for each band (0 for auto)
extern bool   bVolumeDiffusion = true;      // diffuse volue from Low[] to High[] price band
extern int    volumeTimeFrame  = 0;         // time frame only for iVolume()
extern bool   bColorize        = true;      // colorize by volume
extern bool   bShowScale       = true;      // show volume scale
extern int    nScale           = 256;       // number of box for showing scale
extern bool   bBuiltinColorize = false;     // use built-in colorize or custom colorize
extern bool   bNegativeColor   = false;     // invert colors or not
extern bool   bHSVInterp       = true;      // use HSV system for interpolating custom colors
extern color  colBandMax       = Orange;    // custom color of max volume band
extern color  colBandMin       = Black;     // custom color of min volume band
extern double bandMaxLength    = 1.00;      // ratio to window width
extern double bandHeightRatio  = 1.00;      // band height ratio
extern int    maxBand          = 500;       // number of max bands
extern double point            = 0;         // point (ex. 0.01 for USDJPY, 0 uses Point)

//---- indicator buffers

//---- vars
string sIndicatorName;
double g_vol[];

//----------------------------------------------------------------------
int init()
{
    sIndicatorName = "00-PriceBand";

    IndicatorShortName(sIndicatorName);
    
    if (volumeTimeFrame == 0) {
	volumeTimeFrame = Period();
    }
    
    if (point == 0) {
	if (Point == 0 || Symbol() == "GOLD") {
	    point = 0.10;
	} else {
	    point = Point;
	}
    }
    
    if (priceStep == 0) {
	
	switch (Period()) {
	case PERIOD_M1:
	case PERIOD_M5:
	case PERIOD_M15:
	case PERIOD_M30:
	    priceStep = point;
	    break;
	    
	case PERIOD_H1:
	case PERIOD_H4:
	    priceStep = point * 2;
	    break;
	    
	case PERIOD_D1:
	    priceStep = point * 4;
	    break;
	    
	case PERIOD_W1:
	default:
	    priceStep = point * 8;
	    break;
	}
    }
    
    ArrayResize(g_vol, maxBand);

    if (bNegativeColor) {
	colBandMax ^= 0xffffff;
	colBandMin ^= 0xffffff;
    }
    
    Print("Point= ", Point, ", point= ", point, ", priceStep= ", priceStep);
}

//----------------------------------------------------------------------
string getBandName(int i)
{
    return(sIndicatorName + " Band" + i);
}

//----------------------------------------------------------------------
string getScaleSepName()
{
    return(sIndicatorName + " ScaleSep");
}

//----------------------------------------------------------------------
string getScaleUnitName(int i)
{
    return(sIndicatorName + " ScaleUnit" + i);
}

//----------------------------------------------------------------------
void objInit(bool bInit)
{
    int i;
    string s;
    
    // for band
    for (i = 0; i < maxBand; i++) {
	s = getBandName(i);
	if (bInit) {
	    ObjectCreate(s, OBJ_RECTANGLE, 0, 0, 0);
	} else {
	    ObjectDelete(s);
	}
    }
    
    // for scale
    if (bShowScale) {
	s = getScaleSepName();
	if (bInit) {
	    ObjectCreate(s, OBJ_RECTANGLE, 0, 0, 0);
	    ObjectSet(s, OBJPROP_BACK, false);
	} else {
	    ObjectDelete(s);
	}
	for (i = 0; i < nScale; i++) {
	    s = getScaleUnitName(i);
	    if (bInit) {
		ObjectCreate(s, OBJ_RECTANGLE, 0, 0, 0);
	    } else {
		ObjectDelete(s);
	    }
	}
    }
}

//----------------------------------------------------------------------
void deinit()
{
    objInit(false);
}

//----------------------------------------------------------------------
int getR(color col)
{
    return((col >> 0) & 0xff);
}

//----------------------------------------------------------------------
int getG(color col)
{
    return((col >> 8) & 0xff);
}

//----------------------------------------------------------------------
int getB(color col)
{
    return((col >> 16) & 0xff);
}

//----------------------------------------------------------------------
void getRgb(color col, int &r, int &g, int &b)
{
    r = getR(col);
    g = getG(col);
    b = getB(col);
}

//----------------------------------------------------------------------
int clip255(double c)
{
    return(MathMin(MathMax(MathRound(c), 0), 255));
}

//----------------------------------------------------------------------
double clip1(double v)
{
    return(MathMin(MathMax(v, 0.0), 1.0));
}

//----------------------------------------------------------------------
color rgbToColor(double r, double g, double b)
{
    return((clip255(b) << 16) | (clip255(g) << 8) | clip255(r));
}

//----------------------------------------------------------------------
void colorToHsv(color col, double &h, double &s, double &v)
{
    int r = getR(col);
    int g = getG(col);
    int b = getB(col);
    
    int max = MathMax(MathMax(r, g), b);
    int min = MathMin(MathMin(r, g), b);
    double d = max - min;
    
    if (d <= 0) {
	h = 0;
    } else {
	if (max == r) {
	    h = 60.0 * (g - b) / d;
	} else if (max == g) {
	    h = 60.0 * (b - r) / d + 120;
	} else {
	    h = 60.0 * (r - g) / d + 240;
	}
    }
    
    s = d / 255.0;
    
    v = max / 255.0;
}

//----------------------------------------------------------------------
color hsvToColor(double h, double s, double v)
{
    if (h < 0) {
	h += MathCeil(-h / 360) * 360;
    }
    
    int ih = MathFloor(h / 60);
    ih %= 6;
    double f = h / 60.0 - ih;
    double p = v * (1.0 - s) * 255;
    double q = v * (1.0 - f * s) * 255;
    double t = v * (1.0 - (1.0 - f) * s) * 255;
    
    v *= 255;
    
    double r, g, b;
    
    switch (ih) {
    case 0: r = v; g = t; b = p; break;
    case 1: r = q; g = v; b = p; break;
    case 2: r = p; g = v; b = t; break;
    case 3: r = p; g = q; b = v; break;
    case 4: r = t; g = p; b = v; break;
    case 5: r = v; g = p; b = q; break;
    }
    
    return(rgbToColor(r, g, b));
}

//----------------------------------------------------------------------
color getBandColor(double vol)
{
    if (!bColorize) {
	// mono tone
	
	return(colBandMax);
    }
    
    vol = clip1(vol);
    
    // limit in nScale colors
    vol = MathRound(vol * (nScale - 1)) / (nScale - 1);
    
    double r, g, b;
    
    if (!bBuiltinColorize) {
	if (bHSVInterp) {
	    // HSV interpolation from colBandMin to colBandMax
	    double hMin, sMin, vMin;
	    double hMax, sMax, vMax;
	    colorToHsv(colBandMin, hMin, sMin, vMin);
	    colorToHsv(colBandMax, hMax, sMax, vMax);
	    
	    double h = (hMax - hMin) * vol + hMin;
	    double s = (sMax - sMin) * vol + sMin;
	    double v = (vMax - vMin) * vol + vMin;
	    
	    return(hsvToColor(h, s, v));
	} else {
	    // RGB interpolation from colBandMin to colBandMax
	    int rMin, gMin, bMin;
	    int rMax, gMax, bMax;
	    getRgb(colBandMin, rMin, gMin, bMin);
	    getRgb(colBandMax, rMax, gMax, bMax);
	    
	    r = (rMax - rMin) * vol + rMin;
	    g = (gMax - gMin) * vol + gMin;
	    b = (bMax - bMin) * vol + bMin;
	    
	    return(rgbToColor(r, g, b));
	}
    }
    
    // built-in colorize
    double c;
    if (vol <= 0.25) {
	c = vol / 0.25;
	r = 239 * c + 16;
	g = 64 * c + 4;
	b = 0;
    } else if (vol <= 0.5) {
	c = (vol - 0.25) / 0.25;
	r = 255 - 255 * c;
	g = 64;
	b = 255 * c;
    } else if (vol <= 0.75) {
	c = (vol - 0.50) / 0.25;
	r = 0;
	g = 64 + (255 - 64) * c;
	b = 255;
    } else {
	c = (vol - 0.75) / 0.25;
	r = 255 * c;
	g = 255;
	b = 255;
    }
    
    if (bNegativeColor) {
	r = 255 - r;
	g = 255 - g;
	b = 255 - b;
    }
    
    return(rgbToColor(r, g, b));
}

//----------------------------------------------------------------------
void addVolume(double pStart, double pEnd, double dv, double pMin)
{
    int p0 = MathRound(pStart / point);
    int p1 = MathRound(pEnd / point);
    int dir = 1;
    if (p0 > p1) {
	dir = -1;
    }
    for (int i = p0; i != p1; i += dir) {
	int iBand = MathRound((i * point - pMin) / priceStep);
	if (iBand >= 0 && iBand < maxBand) {
	    g_vol[iBand] += dv;
	}
    }
}

//----------------------------------------------------------------------
int start()
{
    int xs0 = WindowFirstVisibleBar();
    int n0 = MathMin(WindowBarsPerChart() + 1, xs0 + 1);
    int bandBars = n0;
    int xs1, n1;
    
    if (nBar > 0) {
	if (!bLeftOrigin) {
	    xs0 = xs0 - n0 + nBar;
	}
	bandBars = MathMin(n0, nBar);
	if (nBar >= n0) {
	    bandMaxLength = 1.0;
	}
	n0 = nBar;
    }
    
    if (volumeTimeFrame == Period()) {
	xs1 = xs0;
	n1 = n0;
    } else {
	xs1 = iBarShift(NULL, volumeTimeFrame, Time[xs0]);
	n1 = MathCeil(n0 * Period() / volumeTimeFrame);
	if (xs1 - n1 + 1 < 0) {
	    n1 = xs1 + 1;
	}
    }
    
    double pMin = Low[iLowest(NULL, 0, MODE_LOW, n0, xs0 - n0 + 1)] - point * 0.1;
    double pMax = High[iHighest(NULL, 0, MODE_HIGH, n0, xs0 - n0 + 1)] + point * 0.1;
    
    double pDiff = pMax - pMin;
    int nBand = MathMin(MathCeil(pDiff / priceStep), maxBand);
    int i, iBar, iBand;
    
    ArrayInitialize(g_vol, 0);
    
    for (i = 0, iBar = xs1; i < n1; i++, iBar--) {
	double pOpen  = iOpen(NULL, volumeTimeFrame, iBar);
	double pHi    = iHigh(NULL, volumeTimeFrame, iBar);
	double pLo    = iLow(NULL, volumeTimeFrame, iBar);
	double pClose = iClose(NULL, volumeTimeFrame, iBar);
	double pVol   = iVolume(NULL, volumeTimeFrame, iBar);
	if (!bVolumeDiffusion) {
	    addVolume(pClose, pClose + point, pVol, pMin);
	} else {
	    int nPoint;
	    double dVol;
	    if (pOpen <= pClose) {
		// 
		// h 7  +       *
		//      |     * *
		// c 5 +-+    *   *
		//     | |    *
		// o 3 +-+  * *
		//      |   * *
		// l 1  +     *
		// 
		nPoint = MathRound((pOpen + 2 * pHi - 2 * pLo - pClose + 1) / point);
		dVol = pVol / nPoint;
		addVolume(pOpen, pLo, dVol, pMin);
		addVolume(pLo, pHi, dVol, pMin);
		addVolume(pHi, pClose, dVol, pMin);
		addVolume(pClose, pClose - point, dVol, pMin);
	    } else {
		// 
		// h 7  +     *
		//      |   * *
		// o 5 +-+  * *
		//     | |    *
		// c 3 +-+    *   *
		//      |     * *
		// l 1  +       *
		// 
		nPoint = MathRound((pClose + 2 * pHi - 2 * pLo - pOpen + 1) / point);
		dVol = pVol / nPoint;
		addVolume(pOpen, pHi, dVol, pMin);
		addVolume(pHi, pLo, dVol, pMin);
		addVolume(pLo, pClose, dVol, pMin);
		addVolume(pClose, pClose + point, dVol, pMin);
	    }
	}
    }
    
    // scale g_vol[] to 0.0 .. 1.0
    double rvMax = g_vol[ArrayMaximum(g_vol)];
    if (rvMax > 0) {
	rvMax = 1.0 / rvMax;
	for (iBand = 0; iBand < maxBand; iBand++) {
	    g_vol[iBand] *= rvMax;
	}
    }
    
    objInit(true);
    
    string s;
    int bs, be;
    double d, lo, hi, vol;
    for (iBand = 0; iBand < maxBand; iBand++) {
	s = getBandName(iBand);
	lo = pMin + (iBand - 0.5 + (1 - bandHeightRatio) / 2.0) * priceStep;
	hi = lo + priceStep * bandHeightRatio;
	vol = g_vol[iBand];
	d = (bandBars - 1) * bandMaxLength;
	if (bLeftOrigin) {
	    bs = xs0;
	    be = MathRound(MathMax(bs - vol * d, 0));
	} else {
	    bs = MathRound(MathMax(xs0 - n0 + 1, 0));
	    be = MathRound(MathMax(bs + vol * d, 0));
	}
	ObjectMove(s, 0, Time[bs], lo);
	ObjectMove(s, 1, Time[be], hi);
	ObjectSet(s, OBJPROP_COLOR, getBandColor(vol));
    }
    
    if (bShowScale) {
	d = 1.0 / nScale * (bandBars - 1) * bandMaxLength;
	
	// scale separator
	if (bLeftOrigin) {
	    bs = xs0;
	    be = MathRound(MathMax(bs - nScale * d, 0));
	} else {
	    bs = MathRound(MathMax(xs0 - n0 + 1, 0));
	    be = MathRound(MathMax(bs + nScale * d, 0));
	}
	s = getScaleSepName();
	ObjectMove(s, 0, Time[bs], pMin);
	ObjectMove(s, 1, Time[be], pMin);
	ObjectSet(s, OBJPROP_COLOR, 0xe0e0e0);
	
	// scale units
	for (int iScale = 0; iScale < nScale; iScale++) {
	    s = getScaleUnitName(iScale);
	    if (bLeftOrigin) {
		bs = MathRound(MathMax(xs0 - iScale * d, 0));
		be = MathRound(MathMax(xs0 - (iScale + 1) * d, 0));
	    } else {
		bs = MathRound(MathMax(xs0 - n0 + 1 + iScale * d, 0));
		be = MathRound(MathMax(xs0 - n0 + 1 + (iScale + 1) * d, 0));
	    }
	    hi = pMin;
	    lo = pMin - MathMax((WindowPriceMax() - WindowPriceMin()) * 0.05, priceStep * 2);
	    vol = (iScale * 1.0) / (nScale - 1);
	    ObjectMove(s, 0, Time[bs], lo);
	    ObjectMove(s, 1, Time[be], hi);
	    ObjectSet(s, OBJPROP_COLOR, getBandColor(vol));
	}
    }
    
    WindowRedraw();
}
