//
// "00-StochasDiv_v103.mq4" -- Stochas Divergence
//
//    Ver. 1.00  2008/12/09(Tue)  initial vestochason
//    Ver. 1.01  2008/12/11(Thu)  bugfixed: longer range won't be checked,
//                                modified error check(div_err)
//    Ver. 1.02  2008/12/12(Fri)  added check of price line (div_oscErr/priceErr)
//                                getLowerLow()/getHigherHigh()
//    Ver. 1.03  2008/12/24(Wed)  added bAllDiv, show all divergence
//                                added bAlertCross, bAlertDiv, bDrawObj, window
//
//
#property  copyright  "00 - 00mql4@gmail.com"
#property  link       "http://www.mql4.com/"

//---- defines
#define DodgerBlue2  0xaa6014
#define Crimson2     0x280d93

//---- indicator settings
#property  indicator_separate_window

#property indicator_minimum  0
#property indicator_maximum  100

#property  indicator_buffers  8

#property  indicator_color1  Green        // 0: Stochas main line
#property  indicator_color2  RosyBrown    // 1: Stochas signal line
#property  indicator_color3  Aqua         // 2: Long signal, cross
#property  indicator_color4  Magenta      // 3: Short signal, cross
#property  indicator_color5  DodgerBlue   // 2: Long signal, divergence
#property  indicator_color6  Crimson      // 3: Short signal, divergence
#property  indicator_color7  Crimson2     // 4: Top peak
#property  indicator_color8  DodgerBlue2  // 5: Bottom peak

#property  indicator_width1  1
#property  indicator_width2  1
#property  indicator_width3  1
#property  indicator_width4  1
#property  indicator_width5  1
#property  indicator_width6  1
#property  indicator_width7  1
#property  indicator_width8  1

#property  indicator_style1  STYLE_SOLID
#property  indicator_style2  STYLE_DOT
#property  indicator_style3  STYLE_SOLID
#property  indicator_style4  STYLE_SOLID
#property  indicator_style5  STYLE_SOLID
#property  indicator_style6  STYLE_SOLID
#property  indicator_style7  STYLE_SOLID
#property  indicator_style8  STYLE_SOLID

//---- indicator parameters
extern int    timeFrame    = 0;          // time frame
extern int    nCheckBars   = 120;        // number of bars to check divergence
extern int    sto_k        = 14;         // %K period
extern int    sto_d        = 8;          // %D period
extern int    sto_slowing  = 8;          // Slowing
extern int    div_mode     = MODE_MAIN;  // 0:MODE_MAIN 1:MODE_SIGNAL
extern bool   bShowAllDiv  = false;      // show all divergence or above/below level_hi/lo
extern double level_lo     = 20;         // when bShowAllDiv is true, show divergence only below this level
extern double level_hi     = 80;         // when bShowAllDiv is true, show divergence only above this level
extern double div_oscErr   = 4.0;        // allowable error along divergence line (oscilator)
extern double div_priceErr = 0.4;        // allowable error along divergence line (PPS), iATR(nCheckBars)*err
extern bool   bAlertCross  = false;      // PlaySound() on signal cross
extern bool   bAlertDiv    = false;      // PlaySound() on divergence
extern bool   bDrawObj     = true;       // draw lines on chart/window
extern int    window       = 0;          // specify indicator window explicitly, 0: auto
extern int    nMaxBars     = 2000;       // maximum number of bars to calculate, 0: no limit

//---- indicator buffers
double BufferStoMain[];     // 0: Stochas main line
double BufferStoSignal[];   // 1: Stochas signal line
double BufferLongCross[];   // 2: Long signal, cross
double BufferShortCross[];  // 3: Short signal, cross
double BufferLongDiv[];     // 4: Long signal, divergence
double BufferShortDiv[];    // 5: Short signal, divergence
double BufferTop[];         // 6: top peak
double BufferBottom[];      // 7: bottom peak

//---- vars
string   sIndicatorName  = "";
string   sIndSelf        = "00-StochasDiv_v103";
string   sPrefix         = "";
int      g_window        = -1;
int      markLong        = 233;
int      markShort       = 234;
int      markDot         = 167;
color    colRegularLong  = DodgerBlue;  // ( 30, 144, 255)= 0x1e90ff
color    colRegularShort = Crimson;     // (220,  20,  60)= 0xdc143c
color    colHiddenLong   = DodgerBlue2;
color    colHiddenShort  = Crimson2;
int      wRegular        = 1;
int      wHidden         = 1;
datetime tAlertLast      = 0;

//----------------------------------------------------------------------
string TimeFrameToStr(int timeFrame)
{
    switch (timeFrame) {
    case 1:     return("M1");
    case 5:     return("M5");
    case 15:    return("M15");
    case 30:    return("M30");
    case 60:    return("H1");
    case 240:   return("H4");
    case 1440:  return("D1");
    case 10080: return("W1");
    case 43200: return("MN");
    }
    
    return("??");
}

//----------------------------------------------------------------------
void init()
{
    if (timeFrame == 0) {
	timeFrame = Period();
    }
    
    string tf = TimeFrameToStr(timeFrame);
    sIndicatorName = sIndSelf + "(" + tf + "," + div_mode + "," + sto_k + "," + sto_d + "," + sto_slowing + ")";
    sPrefix = sIndicatorName;
    
    IndicatorShortName(sIndicatorName);
    
    SetIndexBuffer(0, BufferStoMain);
    SetIndexBuffer(1, BufferStoSignal);
    SetIndexBuffer(2, BufferLongCross);
    SetIndexBuffer(3, BufferShortCross);
    SetIndexBuffer(4, BufferLongDiv);
    SetIndexBuffer(5, BufferShortDiv);
    SetIndexBuffer(6, BufferTop);
    SetIndexBuffer(7, BufferBottom);
    
    SetIndexLabel(0, "Stochas main");
    SetIndexLabel(1, "Stochas signal");
    SetIndexLabel(2, "Long signal, cross");
    SetIndexLabel(3, "Short signal, cross");
    SetIndexLabel(4, "Long signal, divergence");
    SetIndexLabel(5, "Short signal, divergence");
    SetIndexLabel(6, "Top peak");
    SetIndexLabel(7, "Bottom peak");
    
    SetIndexStyle(0, DRAW_LINE);
    SetIndexStyle(1, DRAW_LINE);
    SetIndexStyle(2, DRAW_ARROW);
    SetIndexStyle(3, DRAW_ARROW);
    SetIndexStyle(4, DRAW_ARROW);
    SetIndexStyle(5, DRAW_ARROW);
    SetIndexStyle(6, DRAW_ARROW);
    SetIndexStyle(7, DRAW_ARROW);
    
    SetIndexArrow(2, markLong);
    SetIndexArrow(3, markShort);
    SetIndexArrow(4, markLong);
    SetIndexArrow(5, markShort);
    SetIndexArrow(6, markDot);
    SetIndexArrow(7, markDot);
    
    int n = sto_k + sto_d + sto_slowing;
    
    SetIndexDrawBegin(0, n);
    SetIndexDrawBegin(1, n);
    SetIndexDrawBegin(2, n);
    SetIndexDrawBegin(3, n);
    SetIndexDrawBegin(4, n);
    SetIndexDrawBegin(5, n);
    SetIndexDrawBegin(6, n);
    SetIndexDrawBegin(7, n);
    
    SetLevelValue(0, level_lo);
    SetLevelValue(1, level_hi);
}

//----------------------------------------------------------------------
void deinit()
{
    int n = ObjectsTotal();
    for (int i = n - 1; i >= 0; i--) {
	string sName = ObjectName(i);
	if (StringFind(sName, sPrefix) == 0) {
	    ObjectDelete(sName);
	}
    }
}

//----------------------------------------------------------------------
void objLine(string sName, int win, datetime ts, double ps, datetime te, double pe, color col,
	     int width = 1, int style = STYLE_SOLID, bool bBack = true, bool bRay = false)
{
    ObjectCreate(sName, OBJ_TREND, win, 0, 0);
    ObjectSet(sName, OBJPROP_TIME1,  ts);
    ObjectSet(sName, OBJPROP_PRICE1, ps);
    ObjectSet(sName, OBJPROP_TIME2,  te);
    ObjectSet(sName, OBJPROP_PRICE2, pe);
    ObjectSet(sName, OBJPROP_COLOR,  col);
    ObjectSet(sName, OBJPROP_WIDTH,  width);
    ObjectSet(sName, OBJPROP_STYLE,  style);
    ObjectSet(sName, OBJPROP_BACK,   bBack);
    ObjectSet(sName, OBJPROP_RAY,    bRay);
}	    

//----------------------------------------------------------------------
void objArrow(string sName, int win, datetime t, double p, int arrow, color col, int width = 1)
{
    ObjectCreate(sName, OBJ_ARROW, win, 0, 0);
    ObjectSet(sName, OBJPROP_TIME1,     t);
    ObjectSet(sName, OBJPROP_PRICE1,    p);
    ObjectSet(sName, OBJPROP_ARROWCODE, arrow);
    ObjectSet(sName, OBJPROP_COLOR,     col);
    ObjectSet(sName, OBJPROP_WIDTH,     width);
}

//----------------------------------------------------------------------
double getVal(int i)
{
    if (div_mode == MODE_MAIN) {
	return(BufferStoMain[i]);
    }
    
    return(BufferStoSignal[i]);
}

//----------------------------------------------------------------------
double getLowerPrice(int i)
{
    double hi, lo;
    
    if (div_mode == MODE_MAIN) {
	hi = High[i];
	lo = Low[i];
    } else {
	hi = iMA(NULL, 0, sto_d, 0, MODE_LWMA, PRICE_HIGH, i);
	lo = iMA(NULL, 0, sto_d, 0, MODE_LWMA, PRICE_LOW, i);
    }
    
    return(MathMin(hi, lo));
}

//----------------------------------------------------------------------
double getHigherPrice(int i)
{
    double hi, lo;
    
    if (div_mode == MODE_MAIN) {
	hi = High[i];
	lo = Low[i];
    } else {
	hi = iMA(NULL, 0, sto_d, 0, MODE_LWMA, PRICE_HIGH, i);
	lo = iMA(NULL, 0, sto_d, 0, MODE_LWMA, PRICE_LOW, i);
    }
    
    return(MathMax(hi, lo));
}

//----------------------------------------------------------------------
void intersect(double &x0, double &y0, double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4)
{
    x0 = 0;
    y0 = 0;
    
    double a1, a2, b1, b2;
    
    if (x1 == x2){
	if (x3 != x4) {
	    a1 = (y4 - y3) / (x4 - x3);
	    b1 = y3 - a1 * x3;
	    x0 = x2;
	    y0 = a1 * x0 + b1;
	} else {
	    if (x1 == x3) {
		x0 = x1;
		y0 = y1;
	    }
	}
	
	return;
    }
    
    if (x3 == x4){
	a1 = (y2 - y1) / (x2 - x1);
	b1 = y2 - a1 * x2;
	x0 = x3;
	y0 = a1 * x0 + b1;
	
	return;
    }
    
    a1 = 0;
    b1 = 0;
    a2 = 0;
    b2 = 0;
    
    a1 = (y2 - y1) / (x2 - x1);
    b1 = y2 - a1 * x2;
    
    a2 = (y4 - y3) / (x4 - x3);
    b2 = y3 - a2 * x3;
    
    if (a1 != a2) {
	x0 = (b1 - b2) / (a2 - a1);
	y0 = a1 * x0 + b1;
    } else {
	if (b1 == b2) {
	    x0 = x1;
	    y0 = y1;
	}
    }
}

//----------------------------------------------------------------------
void start()
{
    if (window > 0) {
	g_window = window;
    } else {
	g_window = WindowFind(sIndicatorName);
    }
    
    int limit;
    int counted_bars = IndicatorCounted();
    
    if (counted_bars > 0) {
	counted_bars--;
    }
    
    limit = Bars - counted_bars;
    int limit0 = limit;
    if (nMaxBars > 0) {
	limit = MathMin(limit, nMaxBars);
    }
    
    // clear beyond limits
    for (int i = limit0 - 1; i >= limit; i--) {
	BufferStoMain[i]    = EMPTY_VALUE;
	BufferStoSignal[i]  = EMPTY_VALUE;
	BufferLongCross[i]  = EMPTY_VALUE;
	BufferShortCross[i] = EMPTY_VALUE;
	BufferLongDiv[i]    = EMPTY_VALUE;
	BufferShortDiv[i]   = EMPTY_VALUE;
	BufferTop[i]        = EMPTY_VALUE;
	BufferBottom[i]     = EMPTY_VALUE;
    }
    
    if (timeFrame != Period()) {
	// MTF
	limit = MathMax(limit, timeFrame / Period());
	for (i = limit - 1; i >= 0; i--) {
	    int x = iBarShift(NULL, timeFrame, Time[i]);
	    BufferStoMain[i]    = iCustom(NULL, timeFrame, sIndSelf, 0, nCheckBars, sto_k, sto_d, sto_slowing, div_mode, bShowAllDiv,
					  level_lo, level_hi, div_oscErr, div_priceErr, false, false, true, window, nMaxBars, 0, x); // draw
	    BufferStoSignal[i]  = iCustom(NULL, timeFrame, sIndSelf, 0, nCheckBars, sto_k, sto_d, sto_slowing, div_mode, bShowAllDiv,
					  level_lo, level_hi, div_oscErr, div_priceErr, false, false, false, window, nMaxBars, 1, x);
	    BufferLongCross[i]  = iCustom(NULL, timeFrame, sIndSelf, 0, nCheckBars, sto_k, sto_d, sto_slowing, div_mode, bShowAllDiv,
					  level_lo, level_hi, div_oscErr, div_priceErr, false, false, false, window, nMaxBars, 2, x);
	    BufferShortCross[i] = iCustom(NULL, timeFrame, sIndSelf, 0, nCheckBars, sto_k, sto_d, sto_slowing, div_mode, bShowAllDiv,
					  level_lo, level_hi, div_oscErr, div_priceErr, false, false, false, window, nMaxBars, 3, x);
	    BufferLongDiv[i]    = iCustom(NULL, timeFrame, sIndSelf, 0, nCheckBars, sto_k, sto_d, sto_slowing, div_mode, bShowAllDiv,
					  level_lo, level_hi, div_oscErr, div_priceErr, false, false, false, window, nMaxBars, 4, x);
	    BufferShortDiv[i]   = iCustom(NULL, timeFrame, sIndSelf, 0, nCheckBars, sto_k, sto_d, sto_slowing, div_mode, bShowAllDiv,
					  level_lo, level_hi, div_oscErr, div_priceErr, false, false, false, window, nMaxBars, 5, x);
	    BufferTop[i]        = iCustom(NULL, timeFrame, sIndSelf, 0, nCheckBars, sto_k, sto_d, sto_slowing, div_mode, bShowAllDiv,
					  level_lo, level_hi, div_oscErr, div_priceErr, false, false, false, window, nMaxBars, 6, x);
	    BufferBottom[i]     = iCustom(NULL, timeFrame, sIndSelf, 0, nCheckBars, sto_k, sto_d, sto_slowing, div_mode, bShowAllDiv,
					  level_lo, level_hi, div_oscErr, div_priceErr, false, false, false, window, nMaxBars, 7, x);
	}
	
	// check alert
	if (bAlertCross || bAlertDiv) {
	    bool bFireCross = (BufferLongCross[0] != EMPTY_VALUE || BufferShortCross[0] != EMPTY_VALUE);
	    bool bFireDiv   = (BufferLongDiv[0]   != EMPTY_VALUE || BufferShortDiv[0]   != EMPTY_VALUE);
	    bool bFire = ((bAlertCross && bFireCross) || (bAlertDiv && bFireDiv));
	    if (bFire && tAlertLast != Time[0]) {
		PlaySound("alert.wav");
		tAlertLast = Time[0];
	    }
	}
	
	return;
    }
    
    // timeFrame == Period()
    
    double val1, val2, val3, val4;
    for (i = limit - 1; i >= 0; i--) {
	BufferStoMain[i]    = iStochastic(NULL, 0, sto_k, sto_d, sto_slowing, MODE_SMA, PRICE_CLOSE, MODE_MAIN, i);
	BufferStoSignal[i]  = iStochastic(NULL, 0, sto_k, sto_d, sto_slowing, MODE_SMA, PRICE_CLOSE, MODE_SIGNAL, i);
	BufferLongCross[i]  = EMPTY_VALUE;
	BufferShortCross[i] = EMPTY_VALUE;
	BufferTop[i]        = EMPTY_VALUE;
	BufferBottom[i]     = EMPTY_VALUE;
	
	// check cross
	double main2 = BufferStoMain[i + 2];
	double main1 = BufferStoMain[i + 1];
	
	double sig2  = BufferStoSignal[i + 2];
	double sig1  = BufferStoSignal[i + 1];
	
	datetime t2 = Time[i + 2];
	datetime t1 = Time[i + 1];
	
	double cx, cy;
	intersect(cx, cy, t2, main2, t1, main1, t2, sig2, t1, sig1);
	if (cx > t2 && cx <= t1) {
	    if (cy <= level_lo) {
		BufferLongCross[i] = cy;
	    }
	    if (cy >= level_hi) {
		BufferShortCross[i] = cy;
	    }
	}
	
	// check top/bottom peak
	val4 = getVal(i + 4);
	val3 = getVal(i + 3);
	val2 = getVal(i + 2);
	val1 = getVal(i + 1);
	
	// top peak
	if (val4 <= val3 && val3 <= val2 && val2 > val1) {
	    BufferTop[i + 2] = val2;
	}
	
	// bottom peak
	if (val4 > val3 && val3 > val2 && val2 <= val1) {
	    BufferBottom[i + 2] = val2;
	}
    }
    
    // search divergence on bottom
    for (int i1 = limit - 1; i1 >= 0; i1--) {
	if (BufferBottom[i1] == EMPTY_VALUE) {
	    continue;
	}
	
	for (int hidden = 0; hidden <= 1; hidden++) {
	    val1 = getVal(i1);
	    double p1 = Close[i1];
	    int i2 = -1;
	    for (int j = 1; j <= nCheckBars; j++) {
		x = i1 + j;
		if (BufferBottom[x] == EMPTY_VALUE) {
		    continue;
		}
		val2 = getVal(x);
		double p2 = Close[x];
		if ((!hidden && (val2 > val1 || p2 < p1)) ||
		    (hidden  && (val2 < val1 || p2 > p1))) {
		    continue;
		}
		bool bExceed = false;
		// check div_oscErr
		for (int k = i1 + 1; k < x; k++) {
		    double s = getVal(k);
		    double pred = val1 + (val2 - val1) * (k - i1) / (x - i1);
		    if (pred - s >= div_oscErr) {
			bExceed = true;
			break;
		    }
		}
		// check div_priceErr
		for (k = i1 + 1; k < x; k++) {
		    s = getLowerPrice(k);
		    pred = p1 + (p2 - p1) * (k - i1) / (x - i1);
		    if (pred - s >= iATR(NULL, 0, nCheckBars, k) * div_priceErr) {
			bExceed = true;
			break;
		    }
		}
		if (!bExceed) {
		    i2 = x;
		}
	    }
	    if (i2 >= 0) {
		val1 = getVal(i1);
		val2 = getVal(i2);
		datetime ts = Time[i1];
		datetime tsSig = Time[i1 - 2];
		datetime te = Time[i2];
		double os = getVal(i1);
		double oe = getVal(i2);
		double ps = Close[i1];
		double psSig = Open[i1 - 2];
		double pe = Close[i2];
		color colLine, colArrow;
		int w;
		if (!hidden) {
		    colLine = colRegularLong;
		    colArrow = colRegularLong;
		    w = wRegular;
		} else {
		    colLine = colHiddenLong;
		    colArrow = colRegularLong;
		    w = wHidden;
		}
		if (bShowAllDiv || (os <= level_lo || oe <= level_lo)) {
		    if (bDrawObj) {
			objLine(sPrefix + "line bo" + ts, g_window, ts, os, te, oe, colLine, w);
			objLine(sPrefix + "line bp" + ts, 0, ts, ps, te, pe, colLine, w);
			objArrow(sPrefix + "arrow bp" + tsSig, 0, tsSig, psSig - Point, markLong, colArrow);
		    }
		    BufferLongDiv[i1 - 2] = os;
		}
	    }
	}
    }
    
    // search divergence on top
    for (i1 = limit - 1; i1 >= 0; i1--) {
	if (BufferTop[i1] == EMPTY_VALUE) {
	    continue;
	}
	
	for (hidden = 0; hidden <= 1; hidden++) {
	    val1 = getVal(i1);
	    p1 = Close[i1];
	    i2 = -1;
	    for (j = 1; j <= nCheckBars; j++) {
		x = i1 + j;
		if (BufferTop[x] == EMPTY_VALUE) {
		    continue;
		}
		val2 = getVal(x);
		p2 = Close[x];
		if ((!hidden && (val2 > val1 || p2 < p1)) ||
		    (hidden  && (val2 < val1 || p2 > p1))) {
		    continue;
		}
		bExceed = false;
		// check div_oscErr
		for (k = i1 + 1; k < x; k++) {
		    s = getVal(k);
		    pred = val1 + (val2 - val1) * (k - i1) / (x - i1);
		    if (s - pred >= div_oscErr) {
			bExceed = true;
			break;
		    }
		}
		// check div_priceErr
		for (k = i1 + 1; k < x; k++) {
		    s = getHigherPrice(k);
		    pred = p1 + (p2 - p1) * (k - i1) / (x - i1);
		    if (s - pred >= iATR(NULL, 0, nCheckBars, k) * div_priceErr) {
			bExceed = true;
			break;
		    }
		}
		if (!bExceed) {
		    i2 = x;
		}
	    }
	    if (i2 >= 0) {
		val1 = getVal(i1);
		val2 = getVal(i2);
		ts = Time[i1];
		tsSig = Time[i1 - 2];
		te = Time[i2];
		os = getVal(i1);
		oe = getVal(i2);
		ps = Close[i1];
		psSig = Open[i1 - 2];
		pe = Close[i2];
		if (!hidden) {
		    colLine = colRegularShort;
		    colArrow = colRegularShort;
		    w = wRegular;
		} else {
		    colLine = colHiddenShort;
		    colArrow = colRegularShort;
		    w = wHidden;
		}
		if (bShowAllDiv || (os >= level_hi || oe >= level_hi)) {
		    if (bDrawObj) {
			objLine(sPrefix + "line to" + ts, g_window, ts, os, te, oe, colLine, w);
			objLine(sPrefix + "line tp" + ts, 0, ts, ps, te, pe, colLine, w);
			objArrow(sPrefix + "arrow tp" + tsSig, 0, tsSig, psSig + Point, markShort, colArrow);
		    }
		    BufferShortDiv[i1 - 2] = os;
		}
	    }
	}
    }
    
    if (bAlertCross || bAlertDiv) {
	bFireCross = (BufferLongCross[0] != EMPTY_VALUE || BufferShortCross[0] != EMPTY_VALUE);
	bFireDiv   = (BufferLongDiv[0]   != EMPTY_VALUE || BufferShortDiv[0]   != EMPTY_VALUE);
	bFire = ((bAlertCross && bFireCross) || (bAlertDiv && bFireDiv));
	if (bFire && tAlertLast != Time[0]) {
	    PlaySound("alert.wav");
	    tAlertLast = Time[0];
	}
    }
}
