#property copyright "Volume Levels v2.3.6704. © 2009-2010 Plus."
#property link      "plusfx@ya.ru; skype:plusfx"

#property indicator_chart_window

// Параметры расчетов
extern int RangeMode = 0;						// диапазон: 0 - между вертикальных линий, 1 - последние RangeMinutes минут, 2 = RangeMinutes перед линией
extern int RangeMinutes = 1440;					// количество минут в диапазоне (для некоторых типов указания границ)
extern int ModeStep = 10;						// степень усреднения гистограммы при поиске мод, фактически в 2 раза больше + 1
int PointStep = 1;								// шаг цены
int DataPeriod = 1;								// период для данных, минутки - самые точные
bool ShowHorizon = true;						// показать горизонт данных

// Гистограмма и моды
extern int HGPosition = 1;						// 0 - window left, 1 - window right, 2 - left side, 3 - right side, //TODO: 4 - внутри
extern color HGColor = C'160,224,160';			// цвет гистограммы
extern color ModeColor = Green;					// цвет мод
extern color MaxModeColor = CLR_NONE;			// выделить максимум

int HGLineWidth = 1;							// ширина линий гистограммы
double Zoom = 0;								// масштаб гистограммы, 0 - автомасштаб
int ModeWidth = 1;								// толщина мод
int ModeStyle = STYLE_SOLID;					// стиль мод

// Уровни
extern color ModeLevelColor = C'224,160,160';	// цвет уровня моды
int ModeLevelWidth = 1;							// толщина
extern int ModeLevelStyle = 0;					// стиль

// Служебные
extern string Id = "+vl";						// префикс всех объёктов индикатора

int WaitSeconds = 1;							// минимальное время, в секундах, между обновлениями
int TickMethod = 1;								// метод имитации тиков: 0 - Low >> High, 1 - Open > Low(High) > High(Low) > Close, 2 - HLC, 3 - HL
												// +4 - без учета объёма, +8 - с учетом объёма и длины бара
color TimeFromColor = Blue;						// левая граница диапазона - цвет
int TimeFromStyle = STYLE_DASH;					// левая граница диапазона - стиль

color TimeToColor = Red;						// правая граница диапазона - цвет
int TimeToStyle = STYLE_DASH;					// правая граница диапазона - стиль


string onp, tfn, ttn;
datetime lastTime = 0;	// последнее время запуска

double hgPoint;			// минимальное изменение цены
int modeStep = 0;

bool showHG, showModes, showMaxMode, showModeLevel;


#define ERR_HISTORY_WILL_UPDATED 4066


int init()
{
	onp = Id + " m" + RangeMode + " ";
	tfn = Id + "-from";
	ttn = Id + "-to";

	hgPoint = Point;
	
	bool is5digits = ((Digits == 3) || (Digits == 5)) && (MarketInfo(Symbol(), MODE_PROFITCALCMODE) == 0);
	
	if (PointStep == 0)
	{
		if (is5digits)
			hgPoint = Point * 10.0;
	}
	else
	{
		hgPoint = Point * PointStep;
	}
		
	modeStep = ModeStep * Point / hgPoint;	
	if (is5digits)
		 modeStep *= 10;
	
	// настройки отображения	
	showHG = (HGColor != CLR_NONE) && (HGColor != -16777216);
	showModes = (ModeColor != CLR_NONE) && (ModeColor != -16777216);
	showMaxMode = (MaxModeColor != CLR_NONE) && (MaxModeColor != -16777216);
	showModeLevel = (ModeLevelColor != CLR_NONE) && (ModeLevelColor != -16777216);

	return(0);
}

int start()
{
	if (GlobalVariableGet("+vl-freeze") == 1)
		return;

	datetime currentTime = TimeLocal();
	
	// всегда обновляемся на новом баре...
	if (Volume[0] > 1)
	{
		// ...и не чаще, чем раз в несколько секунд
		if (currentTime - lastTime < WaitSeconds)
			return(0);
	}

	lastTime = currentTime;

	// удаляем старые объекты
	clearChart(onp);

	// определяем рабочий диапазон
	
	datetime timeFrom, timeTo;
	
	if (RangeMode == 0)	// между двух линий
	{
		timeFrom = GetObjectTime1(tfn);
		timeTo = GetObjectTime1(ttn);

		if ((timeFrom == 0) || (timeTo == 0))
		{
			// если границы диапазона не заданы, то устанавливаем их в видимую часть экрана
			datetime timeLeft = getBarTime(WindowFirstVisibleBar());
			datetime timeRight = getBarTime(WindowFirstVisibleBar() - WindowBarsPerChart());
			double r = timeRight - timeLeft;
			
			timeFrom = timeLeft + r / 3;
			timeTo = timeLeft + r * 2 / 3;

			drawVLine(tfn, timeFrom, TimeFromColor, 1, TimeFromStyle, false);
			drawVLine(ttn, timeTo, Crimson, 1, STYLE_DASH, false);
		}

		if (timeFrom > timeTo)
		{
			datetime dt = timeTo;
			timeTo = timeFrom;
			timeFrom = dt;
		}
	}
	else if (RangeMode == 2)	// от правой линии RangeMinutes минут
	{
		timeTo = GetObjectTime1(ttn);

		if (timeTo == 0)
		{
			// если отсчет диапазона не задан, то устанавливаем его в видимую часть экрана
			int bar = MathMax(0, WindowFirstVisibleBar() - WindowBarsPerChart() + 20);
			timeTo = getBarTime(bar);
		}
		else
			bar = iBarShift(NULL, 0, timeTo);

		bar += RangeMinutes/Period();
		timeFrom = Time[bar];// timeTo - RangeMinutes*60;

		drawVLine(tfn, timeFrom, TimeFromColor, 1, TimeFromStyle, false);
		if (ObjectFind(ttn) == -1)
			drawVLine(ttn, timeTo, TimeToColor, 1, TimeToStyle, false);
	}
	else if (RangeMode == 1)
	{
		timeFrom = iTime(Symbol(), PERIOD_M1, RangeMinutes);	// проверить
		timeTo = iTime(Symbol(), PERIOD_M1, 0);
	}
	else
	{
		return(0);
	}

	if (getTimeBar(timeTo) < 0)
		timeTo = iTime(Symbol(), PERIOD_M1, 0);
		
	if (getTimeBar(timeFrom) < 0)
		timeFrom = iTime(Symbol(), PERIOD_M1, 0);

	if (ShowHorizon)
	{
		
		datetime hz = iTime(NULL, DataPeriod, iBars(NULL, DataPeriod) - 1);
		drawVLine(onp + "hz", hz, Red, 1, STYLE_DOT, false);
	}

	int barFrom, barTo, m1BarFrom, m1BarTo;

	if (getRange(timeFrom, timeTo, barFrom, barTo, m1BarFrom, m1BarTo, DataPeriod))
	{
		// получаем гистограмму
		double vh[], hLow;

		int count = getHGByRates(m1BarFrom, m1BarTo, TickMethod, vh, hLow, hgPoint, DataPeriod);

		if (count == 0)
			return(0);
			
		int rp;
		datetime time0;

		double windowTimeRange = WindowBarsPerChart()*Period()*60;
		rp = windowTimeRange*0.1;	// диапазон времени для рисования гистограммы


		// определение масштаба
		double zoom = Zoom*0.000001;
		if (zoom <= 0)
		{
			double maxVolume = vh[ArrayMaximum(vh)];
			zoom = WindowBarsPerChart()*0.1 / maxVolume;
		}

		int bar0;	// бар нулевой отметки гистограммы
		
		if (HGPosition == 0)			// левая граница окна
		{
			bar0 = WindowFirstVisibleBar();
		}
		else if (HGPosition == 1)	// правая граница окна
		{
			bar0 = WindowFirstVisibleBar() - WindowBarsPerChart();
			zoom = -zoom;	// справа переворачиваем
		}
		else if (HGPosition == 2)	// левая граница диапазона
		{
			bar0 = barFrom;
			zoom = -zoom;	// справа переворачиваем
		}
		else 								// 3 - правая граница диапазона
		{
			bar0 = barTo;
		}

		// рисуем
		if (showHG)
			drawHG(onp + "hg ", vh, hLow, bar0, HGColor, HGColor, zoom, HGLineWidth, hgPoint);
	
		if (showModes || showMaxMode || showModeLevel)
			drawModes(vh, hLow, bar0, zoom, hgPoint);
	}
	
	return(0);
}

int deinit()
{
	clearChart(onp);
	//clearChart(tfn);
	//clearChart(ttn);
	return(0);
}

void DrawHLine(string name, double price, color lineColor = Gray, int width = 1, int style = STYLE_SOLID, bool back = true)
{
	if (ObjectFind(name) >= 0)
		ObjectDelete(name);

	if (price > 0 && ObjectCreate(name, OBJ_HLINE, 0, 0, price))
	{
		ObjectSet(name, OBJPROP_COLOR, lineColor);
		ObjectSet(name, OBJPROP_WIDTH, width);
		ObjectSet(name, OBJPROP_STYLE, style);
		ObjectSet(name, OBJPROP_BACK, back);
	}
}

datetime GetObjectTime1(string name)
{
 	if (ObjectFind(name) != -1)
		return(ObjectGet(name, OBJPROP_TIME1));
	else
		return(0);
}


// нарисовать моды гистограммы
void drawModes(double& vh[], double hLow, int barFrom, double zoom, double point)
{
	int modes[], modeCount, j;
	double price;

	// поиск мод
	modeCount = getModesIndexes(vh, modeStep, modes);

	// макс. мода
	double max = 0;
	for (j = 0; j < modeCount; j++)
		if (vh[modes[j]] > max)
			max = vh[modes[j]];

	for (j = 0; j < modeCount; j++)
	{
		double v = zoom*vh[modes[j]];

		// не рисовать коротких линий (меньше бара ТФ), глючит при выделении границ
		if (MathAbs(v) > 0)
		{
			price = hLow + modes[j]*point;
	
			color cl = ModeColor;
			if ((vh[modes[j]] == max) && (showMaxMode))
				cl = MaxModeColor;

			datetime timeFrom = getBarTime(barFrom);
			datetime timeTo = getBarTime(barFrom - v);
		
			drawTrend(onp + "mode " + DoubleToStr(price, Digits), 
				timeFrom, price, timeTo, price, cl, ModeWidth, ModeStyle, false, false);

			if (showModeLevel)
				DrawHLine(onp + "level " + DoubleToStr(price, Digits), price, ModeLevelColor, ModeLevelWidth, ModeLevelStyle, true);

		}
	}
}

int getTimeBar(datetime time, int period = 0)
{
	if (period == 0)
		period = Period();

	int shift = iBarShift(Symbol(), period, time);
	int t = getBarTime(shift, period);
	
	if (t != time) // && shift == 0 ???
		shift = (iTime(Symbol(), period, 0) - time) / 60 / period;

	return(shift);	
}

datetime getBarTime(int shift, int period = 0)
{
	if (period == 0)
		period = Period();

	if (shift >= 0)
		return(iTime(Symbol(), period, shift));
	else
		return(iTime(Symbol(), period, 0) - shift*period*60);
}

/// Очистить график от своих объектов
int clearChart(string prefix)
{
	int obj_total = ObjectsTotal();
	string name;
	
	int count = 0;
	for (int i = obj_total - 1; i >= 0; i--)
	{
		name = ObjectName(i);
		if (StringFind(name, prefix) == 0)
		{
			ObjectDelete(name);
			count++;
		}			
	}
	return(count);
}

void drawVLine(string name, datetime time1, color lineColor = Gray, int width = 1, int style = STYLE_SOLID, bool back = true)
{
	if (ObjectFind(name) >= 0)
		ObjectDelete(name);
		
	ObjectCreate(name, OBJ_VLINE, 0, time1, 0);
	ObjectSet(name, OBJPROP_COLOR, lineColor);
	ObjectSet(name, OBJPROP_BACK, back);
	ObjectSet(name, OBJPROP_STYLE, style);
	ObjectSet(name, OBJPROP_WIDTH, width);
}

void drawTrend(string name, datetime time1, double price1, datetime timeTo, double price2, 
	color lineColor = Gray, int width = 1, int style = STYLE_SOLID, bool back = true, bool ray = true, int window = 0)
{
	if (ObjectFind(name) >= 0)
		ObjectDelete(name);
		
	ObjectCreate(name, OBJ_TREND, window, time1, price1, timeTo, price2);
	ObjectSet(name, OBJPROP_COLOR, lineColor);
	ObjectSet(name, OBJPROP_BACK, back);
	ObjectSet(name, OBJPROP_STYLE, style);
	ObjectSet(name, OBJPROP_WIDTH, width);
	ObjectSet(name, OBJPROP_RAY, ray);
}

// нарисовать гистограмму (+цвет +point)
void drawHG(string prefix, double& h[], double low, int barFrom, color bgColor, color lineColor, double zoom, int width, double point)
{
	double max = h[ArrayMaximum(h)];
	if (max == 0)
		return(0);

	int bgR = (bgColor & 0xFF0000) >> 16;
	int bgG = (bgColor & 0x00FF00) >> 8;
	int bgB = (bgColor & 0x0000FF);

	int lineR = (lineColor & 0xFF0000) >> 16;
	int lineG = (lineColor & 0x00FF00) >> 8;
	int lineB = (lineColor & 0x0000FF);
	
	int dR = lineR - bgR;
	int dG = lineG - bgG;
	int dB = lineB - bgB;

	for (int i = 0; i < ArraySize(h); i++)
	{
		double price = NormalizeDouble(low + i*point, Digits);
		
		int barTo = barFrom - h[i]*zoom;
		
		double fade = h[i] / max;

		int r = MathMax(MathMin(bgR + fade*dR, 255), 0);
		int g = MathMax(MathMin(bgG + fade*dG, 255), 0);
		int b = MathMax(MathMin(bgB + fade*dB, 255), 0);

		color cl = (r << 16) + (g << 8) + b;
		
		datetime timeFrom = getBarTime(barFrom);
		datetime timeTo = getBarTime(barTo);

		if (barFrom != barTo)
			drawTrend(prefix + DoubleToStr(price, Digits), timeFrom, price, timeTo, price, cl, width, STYLE_SOLID, true, false);
	}
}

// получить параметры диапазона
bool getRange(datetime timeFrom, datetime timeTo, int& barFrom, int& barTo, 
	int& p1BarFrom, int& p1BarTo, int period)
{
	// диапазон баров в текущем ТФ (для рисования)

	barFrom = iBarShift(NULL, 0, timeFrom);
	datetime time = Time[barFrom];
	int bar = iBarShift(NULL, 0, time);
	time = Time[bar];
	if (time != timeFrom)
		barFrom--;
											
	barTo = iBarShift(NULL, 0, timeTo);
	time = Time[barTo];
	bar = iBarShift(NULL, 0, time);
	time = Time[bar];
	if (time == timeFrom)
		barTo++;

	if (barFrom < barTo)
		return(false);


	// диапазон баров ТФ period (для получения данных)

	p1BarFrom = iBarShift(NULL, period, timeFrom);
	time = iTime(NULL, period, p1BarFrom);
	if (time != timeFrom)
		p1BarFrom--;
		
	p1BarTo = iBarShift(NULL, period, timeTo);
	time = iTime(NULL, period, p1BarTo);
	if (timeTo == time)
		p1BarTo++;
		
	if (p1BarFrom < p1BarTo)
		return(false);

	return(true);
}

/// Получить гистограмму распределения цен
///		m1BarFrom, m1BarTo - границы диапазона, заданные номерами баров минуток
/// Возвращает:
///		результат - количество цен в гистограмме, 0 - ошибка
///		vh - гистограмма
///		hLow - нижняя граница гистограммы
///		point - шаг цены
///		dataPeriod - таймфрейм данныхint
int getHGByRates(int m1BarFrom, int m1BarTo, int tickMethod, double& vh[], double& hLow, double point, int dataPeriod)
{
	double rates[][6];
	double hHigh;

	// предположительное (и максимальное) количество минуток
	int rCount = getRates(m1BarFrom, m1BarTo, rates, hLow, hHigh, dataPeriod);
	//Print("rCount: " + rCount);
	
	if (rCount != 0)
	{
		hLow = NormalizeDouble(MathRound(hLow / point) * point, Digits);
		hHigh = NormalizeDouble(MathRound(hHigh / point) * point, Digits);
		
		//Print("hLow: " + hLow);
		//Print("hHigh: " + hHigh);

		// инициализируем массив гистограммы
		int hCount = hHigh/point - hLow/point + 1;
		//Print("hCount: " + hCount);
		ArrayResize(vh, hCount);
		ArrayInitialize(vh, 0);

		int iCount = m1BarFrom - m1BarTo + 1;
		int hc = mql_GetHGByRates(rates, rCount, iCount, m1BarTo, tickMethod, point, hLow, hCount, vh);

		//Print("hc: " + hc);

		if (hc == hCount)
			return(hc);
		else
			return(0);
	}
	else
	{
		//Print("Error: no rates");
		return(0);
	}
}

/// Получить гистограмму распределения цен средствами MQL (аналог vlib_GetHGByRates
int mql_GetHGByRates(double& rates[][6], int rcount, int icount, int ishift, int tickMethod, double point, 
	double hLow, int hCount, double& vh[])
{
	int pri;	// индекс цены
	double dv;	// объем на тик

	int hLowI = MathRound(hLow / point);

	//Print(rcount);

	for (int j = 0; j < icount; j++)
	{
		//int i = rcount - 1 - j - ishift;
		int i = j + ishift;

		double o = rates[i][1];
		int oi = MathRound(o/point);

		double h = rates[i][3];
		int hi = MathRound(h/point);

		double l = rates[i][2];
		int li = MathRound(l/point);

		double c = rates[i][4];
		int ci = MathRound(c/point);

		double v = rates[i][5];

		//Print("oi: " + oi);
		//Print("hLowI: " + hLowI);
		//Print("oi-hLowI: " + (oi-hLowI));
		//Print("rate: " + v);

		int rangeMin = hLowI;
		int rangeMax = hLowI + hCount - 1;

		if (tickMethod == 0)						// равная вероятность всех цен бара
		{
			dv = v / (hi - li + 1.0);
			for (pri = li; pri <= hi; pri++)
				vh[pri - hLowI] += dv;
		}
		else if (tickMethod == 1)					// имитация тиков
		{
			if (c >= o)		// бычья свеча
			{
				dv = v / (oi - li + hi - li + hi - ci + 1.0);

				for (pri = oi; pri >= li; pri--)		// open --> low
					vh[pri - hLowI] += dv;

				for (pri = li + 1; pri <= hi; pri++)	// low+1 ++> high
					vh[pri - hLowI] += dv;
				
				for (pri = hi - 1; pri >= ci; pri--)	// high-1 --> close
					vh[pri - hLowI] += dv;
			}
			else			// медвежья свеча
			{
				dv = v / (hi - oi + hi - li + ci - li + 1.0);

				for (pri = oi; pri <= hi; pri++)		// open ++> high
					vh[pri - hLowI] += dv;
				
				for (pri = hi - 1; pri >= li; pri--)	// high-1 --> low
					vh[pri - hLowI] += dv;
				
				for (pri = li + 1; pri <= ci; pri++)	// low+1 ++> close
					vh[pri - hLowI] += dv;
			}
		}
		else if (tickMethod == 2)					// только цены бара
		{
			dv = v / 4.0;
			vh[oi - hLowI] += dv;
			vh[hi - hLowI] += dv;
			vh[li - hLowI] += dv;
			vh[ci - hLowI] += dv;
		}
		else if (tickMethod == 3)					// только хай и лоу
		{
			dv = v / 2.0;
			vh[hi - hLowI] += dv;
			vh[li - hLowI] += dv;
		}
	}
	
	return(hCount);
}

/// Получить моды на основе гистограммы и сглаженной гистограммы (быстрый метод, без сглаживания)
int getModesIndexes(double& vh[], int modeStep, int& modes[]) //, int& maxModeIndex
{
	int modeCount = 0;
	ArrayResize(modes, modeCount);

	int count = ArraySize(vh);
	
	// ищем максимумы по участкам
	for (int i = modeStep; i < count - modeStep; i++)
	{
		int maxFrom = i-modeStep;
		int maxRange = 2*modeStep + 1;
		int maxTo = maxFrom + maxRange - 1;

		int k = ArrayMaximum(vh, maxRange, maxFrom);
		
		if (k == i)
		{
			for (int j = i - modeStep; j <= i + modeStep; j++)
			{
				if (vh[j] == vh[k])
				{
					modeCount++;
					ArrayResize(modes, modeCount);
					modes[modeCount-1] = j;
				}
			}
		}
		
	}

	return(modeCount);
}


/// Получить минутки для заданного диапазона (указывается в номерах баров минуток)
int getRates(int barFrom, int barTo, double& rates[][6], double& ilowest, double& ihighest, int period)
{
	// предположительное (и максимальное) количество минуток
	int iCount = barFrom - barTo + 1;
	
	int count = ArrayCopyRates(rates, NULL, period);
	if (GetLastError() == ERR_HISTORY_WILL_UPDATED)
	{
		return(0);
	}
	else
	{
		if (count >= barFrom - 1)
		{
			ilowest = iLow(NULL, period, iLowest(NULL, period, MODE_LOW, iCount, barTo));
			ihighest = iHigh(NULL, period, iHighest(NULL, period, MODE_HIGH, iCount, barTo));
			//Print("ilowest:" + ilowest);
			//Print("ihighest:" + ihighest);
			return(count);
		}
		else
		{
			return(0);
		}
	}
}


