From 1974eb0eb2eba1f0ccc3617c188680137d56465f Mon Sep 17 00:00:00 2001 From: EarnForex <48102957+EarnForex@users.noreply.github.com> Date: Mon, 20 May 2024 14:06:49 +0200 Subject: [PATCH] 1.23 1. Added Developing Value Area High and Value Area Low lines. 2. Added support for negative prices in MT5. 3. Fixed some bugs with Developing POC not appearing or appearing in wrong places. 4. Fixed a bug with cTrader version when extra sessions could appear. 5. Changed all input parameters for colors in the cTrader version from string to Color. --- MarketProfile.cs | 236 +++++++++++++++++++++++++++++++--------------- MarketProfile.mq4 | 208 +++++++++++++++++++++++++++++++--------- MarketProfile.mq5 | 216 +++++++++++++++++++++++++++++++++--------- 3 files changed, 492 insertions(+), 168 deletions(-) diff --git a/MarketProfile.cs b/MarketProfile.cs index a6062e9..bc3cc75 100644 --- a/MarketProfile.cs +++ b/MarketProfile.cs @@ -8,8 +8,8 @@ // Intraday - should be attached to M1-M15 timeframes. M5 is recommended. // Designed for major currency pairs, but should work also with exotic pairs, CFDs, or commodities. // -// Version 1.22 -// Copyright 2010-2023, EarnForex.com +// Version 1.23 +// Copyright 2010-2024, EarnForex.com // https://www.earnforex.com/metatrader-indicators/MarketProfile/ // ------------------------------------------------------------------------------- using cAlgo.API; @@ -47,6 +47,9 @@ public class MarketProfile : Indicator [Parameter("Enable Developing POC.", DefaultValue = false)] public bool EnableDevelopingPOC { get; set; } + [Parameter("Enable Developing VAH/VAL.", DefaultValue = false)] + public bool EnableDevelopingVAHVAL { get; set; } + [Parameter("ValueAreaPercentage: Percentage of TPO's inside Value Area.", DefaultValue = 70)] public int ValueAreaPercentage { get; set; } @@ -61,19 +64,19 @@ public class MarketProfile : Indicator public int ColorOpacity { get; set; } [Parameter("SingleColor: if ColorScheme is set to Single_Color.", DefaultValue = "Blue")] - public string SingleColor { get; set; } + public Color SingleColor { get; set; } [Parameter("ColorBullBear: If true, colors are from bars' direction.", DefaultValue = false)] public bool ColorBullBear { get; set; } [Parameter("MedianColor", DefaultValue = "White")] - public string MedianColor { get; set; } + public Color MedianColor { get; set; } [Parameter("ValueAreaSidesColor", DefaultValue = "White")] - public string ValueAreaSidesColor { get; set; } + public Color ValueAreaSidesColor { get; set; } [Parameter("ValueAreaHighLowColor", DefaultValue = "White")] - public string ValueAreaHighLowColor { get; set; } + public Color ValueAreaHighLowColor { get; set; } [Parameter("MedianStyle", DefaultValue = LineStyle.Solid)] public LineStyle MedianStyle { get; set; } @@ -124,7 +127,7 @@ public class MarketProfile : Indicator public bool ShowKeyValues { get; set; } [Parameter("KeyValuesColor: color for VAH, VAL, POC printout.", DefaultValue = "White")] - public string KeyValuesColor { get; set; } + public Color KeyValuesColor { get; set; } [Parameter("KeyValuesSize: font size for VAH, VAL, POC printout.", DefaultValue = 12)] public int KeyValuesSize { get; set; } @@ -133,7 +136,7 @@ public class MarketProfile : Indicator public single_print_type ShowSinglePrint { get; set; } [Parameter("SinglePrintColor", DefaultValue = "Gold")] - public string SinglePrintColor { get; set; } + public Color SinglePrintColor { get; set; } [Parameter("SinglePrintRays: mark Single Print edges with rays.", DefaultValue = false)] public bool SinglePrintRays { get; set; } @@ -145,7 +148,7 @@ public class MarketProfile : Indicator public int SinglePrintRayWidth { get; set; } [Parameter("ProminentMedianColor", DefaultValue = "Yellow")] - public string ProminentMedianColor { get; set; } + public Color ProminentMedianColor { get; set; } [Parameter("ProminentMedianStyle", DefaultValue = LineStyle.Solid)] public LineStyle ProminentMedianStyle { get; set; } @@ -213,13 +216,13 @@ public class MarketProfile : Indicator public bool AlertOnGapCross { get; set; } [Parameter("AlertArrowColorPB: arrow color for price break alerts.", DefaultValue = "Red")] - public string AlertArrowColorPB { get; set; } + public Color AlertArrowColorPB { get; set; } [Parameter("AlertArrowColorCC: arrow color for candle close alerts.", DefaultValue = "Blue")] - public string AlertArrowColorCC { get; set; } + public Color AlertArrowColorCC { get; set; } [Parameter("AlertArrowColorGC: arrow color for gap crossover alerts.", DefaultValue = "Yellow")] - public string AlertArrowColorGC { get; set; } + public Color AlertArrowColorGC { get; set; } [Parameter("=== Intraday settings", DefaultValue = "=================")] @@ -299,6 +302,18 @@ public class MarketProfile : Indicator [Output("Developing POC 2", LineColor = "Green", LineStyle = LineStyle.Solid, PlotType = PlotType.DiscontinuousLine, Thickness = 5)] public IndicatorDataSeries DevelopingPOC_2 { get; set; } + [Output("Developing VAH 1", LineColor = "Goldenrod", LineStyle = LineStyle.Solid, PlotType = PlotType.DiscontinuousLine, Thickness = 5)] + public IndicatorDataSeries DevelopingVAH_1 { get; set; } + + [Output("Developing VAH 2", LineColor = "Goldenrod", LineStyle = LineStyle.Solid, PlotType = PlotType.DiscontinuousLine, Thickness = 5)] + public IndicatorDataSeries DevelopingVAH_2 { get; set; } + + [Output("Developing VAL 1", LineColor = "Salmon", LineStyle = LineStyle.Solid, PlotType = PlotType.DiscontinuousLine, Thickness = 5)] + public IndicatorDataSeries DevelopingVAL_1 { get; set; } + + [Output("Developing VAL 2", LineColor = "Salmon", LineStyle = LineStyle.Solid, PlotType = PlotType.DiscontinuousLine, Thickness = 5)] + public IndicatorDataSeries DevelopingVAL_2 { get; set; } + #endregion #region Enums @@ -939,12 +954,16 @@ protected override void OnTimer() if (Session == session_period.Intraday) FirstRunDone = false; // Turn off because FirstRunDone should be false for Intraday sessions to draw properly in the past. - if (EnableDevelopingPOC) + if ((EnableDevelopingPOC) || (EnableDevelopingVAHVAL)) { for (int i = Bars.Count - 1; i >= 0; i--) // Clean indicator buffers. { DevelopingPOC_1[i] = double.NaN; DevelopingPOC_2[i] = double.NaN; + DevelopingVAH_1[i] = double.NaN; + DevelopingVAH_2[i] = double.NaN; + DevelopingVAL_1[i] = double.NaN; + DevelopingVAL_2[i] = double.NaN; } } } @@ -1392,7 +1411,7 @@ private DateTime PutDot(double price, int start_bar, int range, int bar, DateTim offset2 = 0x000003; break; case color_scheme.Single_Color: - colour = Color.FromName(SingleColor); + colour = SingleColor; offset1 = 0; offset2 = 0; break; @@ -1510,32 +1529,32 @@ private Color CalculateProperColor() case color_scheme.Single_Color: if (CurrentBarDirection == bar_direction.Bullish) { - colour = Color.FromName(SingleColor); + colour = SingleColor; } else if (CurrentBarDirection == bar_direction.Bearish) { - int colour_hex = 0x00FFFFFF - Convert.ToInt32(Color.FromName(SingleColor).ToHexString()); + int colour_hex = 0x00FFFFFF - Convert.ToInt32(SingleColor.ToHexString()); colour = Color.FromHex(colour_hex.ToString("X4")); } else if (CurrentBarDirection == bar_direction.Neutral) { - int colour_hex = Math.Max(Convert.ToInt32(Color.FromName(SingleColor).ToHexString()), 0x00FFFFFF - Convert.ToInt32(Color.FromName(SingleColor).ToHexString())); + int colour_hex = Math.Max(Convert.ToInt32(SingleColor.ToHexString()), 0x00FFFFFF - Convert.ToInt32(SingleColor.ToHexString())); colour = Color.FromHex(colour_hex.ToString("X4")); } break; default: if (CurrentBarDirection == bar_direction.Bullish) { - colour = Color.FromName(SingleColor); + colour = SingleColor; } else if (CurrentBarDirection == bar_direction.Bearish) { - int colour_hex = 0x00FFFFFF - Convert.ToInt32(Color.FromName(SingleColor).ToHexString()); + int colour_hex = 0x00FFFFFF - Convert.ToInt32(SingleColor.ToHexString()); colour = Color.FromHex(colour_hex.ToString("X4")); } else if (CurrentBarDirection == bar_direction.Neutral) { - int colour_hex = Math.Max(Convert.ToInt32(Color.FromName(SingleColor).ToHexString()), 0x00FFFFFF - Convert.ToInt32(Color.FromName(SingleColor).ToHexString())); + int colour_hex = Math.Max(Convert.ToInt32(SingleColor.ToHexString()), 0x00FFFFFF - Convert.ToInt32(SingleColor.ToHexString())); colour = Color.FromHex(colour_hex.ToString("X4")); } break; @@ -1560,7 +1579,7 @@ private void CheckRectangles() ObjectCleanup(MPR_Array[i].name + "_"); // Buffer cleanup for the Developing POC. - if (EnableDevelopingPOC) + if ((EnableDevelopingPOC) || (EnableDevelopingVAHVAL)) { int sessionstart = Bars.OpenTimes.GetIndexByTime(MPR_Array[i].RectangleTimeMin); int sessionend = Bars.OpenTimes.GetIndexByTime(MPR_Array[i].RectangleTimeMax); @@ -1571,6 +1590,10 @@ private void CheckRectangles() { DevelopingPOC_1[j] = double.NaN; DevelopingPOC_2[j] = double.NaN; + DevelopingVAH_1[j] = double.NaN; + DevelopingVAH_2[j] = double.NaN; + DevelopingVAL_1[j] = double.NaN; + DevelopingVAL_2[j] = double.NaN; } } @@ -1671,7 +1694,7 @@ private void MPR_Process(int i) } // Buffer cleanup for the Developing POC. Should be run only for a changed rectangle, which isn't brand new. - if (EnableDevelopingPOC && rectangle_changed && mp.RectangleTimeMax != DateTime.MinValue) + if ((EnableDevelopingPOC || EnableDevelopingVAHVAL) && rectangle_changed && mp.RectangleTimeMax != DateTime.MinValue) { int _sessionstart = Bars.OpenTimes.GetIndexByTime(mp.RectangleTimeMin); int _sessionend = Bars.OpenTimes.GetIndexByTime(mp.RectangleTimeMax); @@ -1682,7 +1705,12 @@ private void MPR_Process(int i) { DevelopingPOC_1[j] = double.NaN; DevelopingPOC_2[j] = double.NaN; + DevelopingVAH_1[j] = double.NaN; + DevelopingVAH_2[j] = double.NaN; + DevelopingVAL_1[j] = double.NaN; + DevelopingVAL_2[j] = double.NaN; } + mp.prev_Time0 = DateTime.MinValue; } mp.RectangleTimeMax = (mp.t1 > mp.t2 ? mp.t1 : mp.t2); @@ -1809,7 +1837,7 @@ private void MPR_Process(int i) if (Bars[Bars.Count - 1].OpenTime < mp.RectangleTimeMax) RememberSession[RememberSession.Count - 1].End = Bars[Bars.Count - 1].OpenTime; else RememberSession[RememberSession.Count - 1].End = mp.RectangleTimeMax; - if (!new_bars_are_not_within_rectangle || current_bar_changed_within_boundaries || rectangle_changed_and_recalc_is_due || (EnableDevelopingPOC && rectangle_changed) || + if (!new_bars_are_not_within_rectangle || current_bar_changed_within_boundaries || rectangle_changed_and_recalc_is_due || ((EnableDevelopingPOC || EnableDevelopingVAHVAL) && rectangle_changed) || (mp.Number != i && RaysUntilIntersection != ways_to_stop_rays.Stop_No_Rays && (ShowMedianRays != sessions_to_draw_rays.None || ShowValueAreaRays != sessions_to_draw_rays.None))) ProcessSession(sessionstart, sessionend, i, mp); @@ -2163,7 +2191,7 @@ private bool ProcessSession(int sessionstart, int sessionend, int i, CRectangleM // Go through all prices again, check TPOs - whether they are single and whether they aren't bordered by another single print TPOs? if (SinglePrintRays) { - Color spr_color = Color.FromName(SinglePrintColor); // Normal ray color. + Color spr_color = SinglePrintColor; // Normal ray color. if (HideRaysFromInvisibleSessions && Bars[Chart.FirstVisibleBarIndex].OpenTime >= Bars[sessionstart].OpenTime) spr_color = Color.Transparent; // Hide rays if behind the screen. @@ -2205,8 +2233,8 @@ private bool ProcessSession(int sessionstart, int sessionend, int i, CRectangleM } } - if (EnableDevelopingPOC) - CalculateDevelopingPOC(sessionstart, sessionend, rectangle); // Developing POC if necessary. + if ((EnableDevelopingPOC) || (EnableDevelopingVAHVAL)) + CalculateDevelopingPOCVAHVAL(sessionstart, sessionend, rectangle); // Developing POC if necessary. double TotalTPOdouble = TotalTPO; @@ -2270,7 +2298,7 @@ private bool ProcessSession(int sessionstart, int sessionend, int i, CRectangleM time_start = Bars[sessionstart].OpenTime; } - Color m_color = Color.FromName(MedianColor); + Color m_color = MedianColor; int m_thickness = MedianWidth; LineStyle m_linestyle = MedianStyle; @@ -2289,28 +2317,28 @@ private bool ProcessSession(int sessionstart, int sessionend, int i, CRectangleM // Draw a new one. Chart.DrawTrendLine(va_leftside_name, time_start, PriceOfMaxRange + up_offset * onetick, time_start, PriceOfMaxRange - down_offset * onetick + onetick, - Color.FromName(ValueAreaSidesColor), ValueAreaSidesWidth, ValueAreaSidesStyle); + ValueAreaSidesColor, ValueAreaSidesWidth, ValueAreaSidesStyle); string va_rightside_name = rectangle_prefix + "VA_RightSide" + Suffix + LastName; Chart.RemoveObject(va_rightside_name); // Draw a new one. Chart.DrawTrendLine(va_rightside_name, time_end, PriceOfMaxRange + up_offset * onetick, time_end, PriceOfMaxRange - down_offset * onetick + onetick, - Color.FromName(ValueAreaSidesColor), ValueAreaSidesWidth, ValueAreaSidesStyle); + ValueAreaSidesColor, ValueAreaSidesWidth, ValueAreaSidesStyle); string va_top_name = rectangle_prefix + "VA_Top" + Suffix + LastName; Chart.RemoveObject(va_top_name); // Draw a new one. Chart.DrawTrendLine(va_top_name, time_start, PriceOfMaxRange + up_offset * onetick, time_end, PriceOfMaxRange + up_offset * onetick, - Color.FromName(ValueAreaHighLowColor), ValueAreaHighLowWidth, ValueAreaHighLowStyle); + ValueAreaHighLowColor, ValueAreaHighLowWidth, ValueAreaHighLowStyle); string va_bottom_name = rectangle_prefix + "VA_Bottom" + Suffix + LastName; Chart.RemoveObject(va_bottom_name); // Draw a new one. Chart.DrawTrendLine(va_bottom_name, time_start, PriceOfMaxRange - down_offset * onetick + onetick, time_end, PriceOfMaxRange - down_offset * onetick + onetick, - Color.FromName(ValueAreaHighLowColor), ValueAreaHighLowWidth, ValueAreaHighLowStyle); + ValueAreaHighLowColor, ValueAreaHighLowWidth, ValueAreaHighLowStyle); // VAH, VAL, and POC printout. if (ShowKeyValues) @@ -2381,7 +2409,7 @@ private bool ProcessIntradaySession(int sessionstart, int sessionend, int i) { IntradaySessionCount_tmp++; } - + for (int intraday_i = 0; intraday_i < IntradaySessionCount_tmp; intraday_i++) { // Continue was triggered during the special case iteration. @@ -2494,7 +2522,7 @@ private bool ProcessIntradaySession(int sessionstart, int sessionend, int i) sessiontime = Bars[sessionstart].OpenTime.AddMinutes(TimeShiftMinutes).Hour * 60 + Bars[sessionstart].OpenTime.AddMinutes(TimeShiftMinutes).Minute; } // This check is necessary because sessionstart may pass to the wrong day in some cases. - if (sessionstart > remember_sessionstart) + if (sessionstart < remember_sessionstart) sessionstart = remember_sessionstart; } else @@ -2622,8 +2650,6 @@ private bool ProcessIntradaySession(int sessionstart, int sessionend, int i) { sessionstart = sessionend; - int _start_time = Bars[sessionstart].OpenTime.AddMinutes(TimeShiftMinutes).Hour * 60 + Bars[sessionstart].OpenTime.AddMinutes(TimeShiftMinutes).Minute; - while (sessionstart >= 0 && (((Bars[sessionstart].OpenTime.AddMinutes(TimeShiftMinutes).Hour * 60 + Bars[sessionstart].OpenTime.AddMinutes(TimeShiftMinutes).Minute >= ID[intraday_i].StartTime) && // Same day - for cases when the day does not contain intraday session start time. Alternatively, continue looking for the sessionstart bar if we moved from Saturday to Friday with Append_Saturday_Sunday and for XX:XX-00:00 session. @@ -2778,7 +2804,7 @@ private void CheckRays() if (Bars[Chart.FirstVisibleBarIndex].OpenTime >= RememberSession[i].Start) // Too old. ctl.Color = Color.Transparent; // Hide else - ctl.Color = Color.FromName(SinglePrintColor); // Unhide + ctl.Color = SinglePrintColor; // Unhide } } @@ -3058,11 +3084,11 @@ private void CheckAndDrawArrow(int n, double level, string ray_name) } // Creates an arrow object and sets its properties. - void CreateArrowObject(string name, DateTime time, double price, string colour, ChartIconType type) + void CreateArrowObject(string name, DateTime time, double price, Color colour, ChartIconType type) { string obj_name = name + ArrowsCounter.ToString(); ArrowsCounter++; - Chart.DrawIcon(obj_name, type, time, price, Color.FromName(colour)); + Chart.DrawIcon(obj_name, type, time, price, colour); } #endregion @@ -3098,7 +3124,7 @@ private void CheckRayIntersections(string obj_name, int start_j) //+------------------------------------------------------------------+ private void ValuePrintOut(string obj_name, DateTime time, double price, cAlgo.API.HorizontalAlignment ha, VerticalAlignment va) { - ChartText text = Chart.FindObject(obj_name) as ChartText; + ChartText text = (ChartText)Chart.FindObject(obj_name); // Find object if it exists. if (text != null) { @@ -3108,7 +3134,7 @@ private void ValuePrintOut(string obj_name, DateTime time, double price, cAlgo.A } else { - text = Chart.DrawText(obj_name, price.ToString("F" + Symbol.Digits.ToString()), time, price, Color.FromName(KeyValuesColor)); + text = Chart.DrawText(obj_name, price.ToString("F" + Symbol.Digits.ToString()), time, price, KeyValuesColor); text.FontSize = KeyValuesSize; text.HorizontalAlignment = ha; text.VerticalAlignment = va; @@ -3134,13 +3160,13 @@ private void PutSinglePrintMark(double price, int sessionstart, string rectangle string LastName = LastNameStart + price.ToString("F" + Symbol.Digits.ToString()); string mpsp_name = rectangle_prefix + "MPSP" + Suffix + LastName; - ChartRectangle mpsp_r = Chart.FindObject(mpsp_name) as ChartRectangle; + ChartRectangle mpsp_r = (ChartRectangle)Chart.FindObject(mpsp_name); // If already there - ignore. if (mpsp_r != null) return; - mpsp_r = Chart.DrawRectangle(mpsp_name, Bars[t1].OpenTime, price, Bars[t2].OpenTime, price - onetick, Color.FromName(SinglePrintColor)); + mpsp_r = Chart.DrawRectangle(mpsp_name, Bars[t1].OpenTime, price, Bars[t2].OpenTime, price - onetick, SinglePrintColor); if (fill) mpsp_r.IsFilled = true; } @@ -3183,7 +3209,7 @@ private void PutSinglePrintRay(double price, int sessionstart, string rectangle_ string LastName = LastNameStart + price.ToString("F" + Symbol.Digits.ToString()); string mpspr_name = rectangle_prefix + "MPSPR" + Suffix + LastName; - ChartTrendLine mpspr_tl = Chart.FindObject(mpspr_name) as ChartTrendLine; + ChartTrendLine mpspr_tl = (ChartTrendLine)Chart.FindObject(mpspr_name); // If already there - ignore. if (mpspr_tl != null) @@ -3321,12 +3347,12 @@ private void RedrawLastSession() #endregion - #region CalculateDevelopingPOC + #region CalculateDevelopingPOCVAHVAL //+------------------------------------------------------------------+ //| Go through all prices on all N session bars from 1st to kth bar, | //| where k = 1..N. | //+------------------------------------------------------------------+ - private void CalculateDevelopingPOC(int sessionstart, int sessionend, CRectangleMP rectangle) + private void CalculateDevelopingPOCVAHVAL(int sessionstart, int sessionend, CRectangleMP rectangle) { // Cycle through all possible end bars to calculate the Developing POC. for (int max_bar = sessionstart; max_bar <= sessionend; max_bar++) @@ -3351,6 +3377,13 @@ private void CalculateDevelopingPOC(int sessionstart, int sessionend, CRectangle int DevMaxRange = 0; // Maximum range for the Developing POC. double PriceOfMaxRange = double.NaN; + // Will be necessary for Developing VAH/VAL calculation. + int TotalTPO = 0; // Total amount of dots (TPO's). + // Possible price levels if multiplied to integer. + double dmax = (LocalMax - LocalMin) / onetick; + int max = (int)Math.Round(dmax + 2); // + 2 because further we will be possibly checking array at SessionMax + 1. + int[] TPOperPrice = new int[max]; + // Cycle by price inside the local boundaries: for (double price = LocalMax; price >= LocalMin; price -= onetick) { @@ -3371,52 +3404,101 @@ private void CalculateDevelopingPOC(int sessionstart, int sessionend, CRectangle PriceOfMaxRange = price; DistanceToCenter = Math.Abs(price - (LocalMin + (LocalMax - LocalMin) / 2)); } + // Remember the number of encountered bars for this price for Developing VAH/VAL. + int idx = (int)Math.Round((price - LocalMin) / onetick); + TPOperPrice[idx]++; + TotalTPO++; range++; } } } + if (EnableDevelopingVAHVAL) + { + double TotalTPOdouble = TotalTPO; + // Calculate amount of TPO's in the Value Area. + int ValueControlTPO = (int)Math.Round(TotalTPOdouble * ValueAreaPercentage_double); + // Start with the TPO's of the Median. + int index = (int)((PriceOfMaxRange - LocalMin) / onetick); + if (index < 0) continue; // Data not yet ready. + int TPOcount = TPOperPrice[index]; + + // Go through the price levels above and below median adding the biggest to TPO count until the 70% of TPOs are inside the Value Area. + int up_offset = 1; + int down_offset = 1; + while (TPOcount < ValueControlTPO) + { + double abovePrice = PriceOfMaxRange + up_offset * onetick; + double belowPrice = PriceOfMaxRange - down_offset * onetick; + + // If belowPrice is out of the session's range then we should add only abovePrice's TPO's, and vice versa. + index = (int)Math.Round((abovePrice - LocalMin) / onetick); + int index2 = (int)Math.Round((belowPrice - LocalMin) / onetick); + if ((belowPrice < LocalMin || TPOperPrice[index] >= TPOperPrice[index2]) && abovePrice <= LocalMax) + { + TPOcount += TPOperPrice[index]; + up_offset++; + } + else if (belowPrice >= LocalMin) + { + TPOcount += TPOperPrice[index2]; + down_offset++; + } + // Cannot proceed - too few data points. + else if (TPOcount < ValueControlTPO) + { + break; + } + } + DistributeBetweenTwoBuffers(DevelopingVAH_1, DevelopingVAH_2, max_bar, PriceOfMaxRange + up_offset * onetick); + DistributeBetweenTwoBuffers(DevelopingVAL_1, DevelopingVAL_2, max_bar, PriceOfMaxRange - down_offset * onetick + onetick); + } + if (EnableDevelopingPOC) DistributeBetweenTwoBuffers(DevelopingPOC_1, DevelopingPOC_2, max_bar, PriceOfMaxRange); + } + } + + #endregion - // Both buffer are empty: - if (double.IsNaN(DevelopingPOC_1[max_bar - 1]) && double.IsNaN(DevelopingPOC_2[max_bar - 1])) + // Used for Developing POC, VAH, and VAL lines. + void DistributeBetweenTwoBuffers(IndicatorDataSeries buff1, IndicatorDataSeries buff2, int bar, double price) + { + // Both buffer are empty: + if (double.IsNaN(buff1[bar - 1]) && double.IsNaN(buff2[bar - 1])) + { + buff1[bar] = price; // Starting with the first one. + buff2[bar] = double.NaN; // The second is initialized to an empty value. + } + // Buffer #1 already had a value, + else if (!double.IsNaN(buff1[bar - 1])) + { + // and it is different from what we get now. + if (buff1[bar - 1] != price) { - DevelopingPOC_1[max_bar] = PriceOfMaxRange; // Starting with the first one. - DevelopingPOC_2[max_bar] = double.NaN; // The second is initialized to an empty value. + buff2[bar] = price; // Use new buffer to get an interrupted shift of lines. + buff1[bar] = double.NaN; } - // Buffer #1 already had a value, - else if (!double.IsNaN(DevelopingPOC_1[max_bar - 1])) + else // and it is the same price: { - // and it is different from what we get now. - if (DevelopingPOC_1[max_bar - 1] != PriceOfMaxRange) - { - DevelopingPOC_2[max_bar] = PriceOfMaxRange; // Use new buffer to get an interrupted shift of lines. - DevelopingPOC_1[max_bar] = double.NaN; - } - else // and it is the same price: - { - DevelopingPOC_1[max_bar] = PriceOfMaxRange; // Use the same buffer. - DevelopingPOC_2[max_bar] = double.NaN; - } + buff1[bar] = price; // Use the same buffer. + buff2[bar] = double.NaN; } - // Buffer #2 already had a value, - else + } + // Buffer #2 already had a value, + else + { + // and it is different from what we get now. + if (buff2[bar - 1] != price) { - // and it is different from what we get now. - if (DevelopingPOC_2[max_bar - 1] != PriceOfMaxRange) - { - DevelopingPOC_1[max_bar] = PriceOfMaxRange; // Use new buffer to get an interrupted shift of lines. - DevelopingPOC_2[max_bar] = double.NaN; - } - else // and it is the same price: - { - DevelopingPOC_2[max_bar] = PriceOfMaxRange; // Use the same buffer. - DevelopingPOC_1[max_bar] = double.NaN; - } + buff1[bar] = price; // Use new buffer to get an interrupted shift of lines. + buff2[bar] = double.NaN; + } + else // and it is the same price: + { + buff2[bar] = price; // Use the same buffer. + buff1[bar] = double.NaN; } } } - #endregion - #region CheckAlerts //+------------------------------------------------------------------+ //| Checks all alert conditions and issues alerts if needed. | diff --git a/MarketProfile.mq4 b/MarketProfile.mq4 index 250ac97..b787b7a 100644 --- a/MarketProfile.mq4 +++ b/MarketProfile.mq4 @@ -1,11 +1,11 @@ //+------------------------------------------------------------------+ //| MarketProfile.mq4 | -//| Copyright © 2010-2023, EarnForex.com | +//| Copyright © 2010-2024, EarnForex.com | //| https://www.earnforex.com/ | //+------------------------------------------------------------------+ #property copyright "EarnForex.com" #property link "https://www.earnforex.com/metatrader-indicators/MarketProfile/" -#property version "1.22" +#property version "1.23" #property strict #property description "Displays the Market Profile indicator for intraday, daily, weekly, or monthly trading sessions." @@ -19,9 +19,9 @@ // Rectangle session - a rectangle's name should start with 'MPR' and must not contain an underscore ('_'). //+------------------------------------------------------------------+ #property indicator_chart_window -// Two buffers are used for the Developing POC display because a single buffer wouldn't support an interrupting line. -#property indicator_plots 2 -#property indicator_buffers 2 +// Two buffers are used for the Developing POC and Developing VAH/VAL display because a single buffer wouldn't support an interrupting line. +#property indicator_plots 6 +#property indicator_buffers 6 #property indicator_color1 clrGreen #property indicator_color2 clrGreen #property indicator_width1 5 @@ -32,6 +32,26 @@ #property indicator_style2 STYLE_SOLID #property indicator_label1 "Developing POC" #property indicator_label2 "Developing POC" +#property indicator_color3 clrGoldenrod +#property indicator_color4 clrGoldenrod +#property indicator_width3 5 +#property indicator_width4 5 +#property indicator_type3 DRAW_LINE +#property indicator_type4 DRAW_LINE +#property indicator_style3 STYLE_SOLID +#property indicator_style4 STYLE_SOLID +#property indicator_label3 "Developing VAH" +#property indicator_label4 "Developing VAH" +#property indicator_color5 clrSalmon +#property indicator_color6 clrSalmon +#property indicator_width5 5 +#property indicator_width6 5 +#property indicator_type5 DRAW_LINE +#property indicator_type6 DRAW_LINE +#property indicator_style5 STYLE_SOLID +#property indicator_style6 STYLE_SOLID +#property indicator_label5 "Developing VAL" +#property indicator_label6 "Developing VAL" enum color_scheme { @@ -112,8 +132,9 @@ input session_period Session = Daily; input datetime StartFromDate = __DATE__; // StartFromDate: lower priority. input bool StartFromCurrentSession = true; // StartFromCurrentSession: higher priority. input int SessionsToCount = 2; // SessionsToCount: Number of sessions to count Market Profile. -input bool SeamlessScrollingMode = false; // SeamlessScrollingMode: show sessions on current screen. -input bool EnableDevelopingPOC = false; // Enable Developing POC. +input bool SeamlessScrollingMode = false; // SeamlessScrollingMode: Show sessions on current screen. +input bool EnableDevelopingPOC = false; // Enable Developing POC +input bool EnableDevelopingVAHVAL = false; // Enable Developing VAH/VAL input int ValueAreaPercentage = 70; // ValueAreaPercentage: Percentage of TPO's inside Value Area. input group "Colors and looks" @@ -269,12 +290,13 @@ public: CRectangleMP(string); ~CRectangleMP(void) {}; void Process(int); + void ResetPrevTime0(); }; CRectangleMP* MPR_Array[]; int mpr_total = 0; uint LastRecalculationTime = 0; -double DevelopingPOC_1[], DevelopingPOC_2[]; // Indicator buffers for Developing POC. +double DevelopingPOC_1[], DevelopingPOC_2[], DevelopingVAH_1[], DevelopingVAH_2[], DevelopingVAL_1[], DevelopingVAL_2[]; // Indicator buffers for Developing POC and VAH/VAL. //+------------------------------------------------------------------+ //| Custom indicator initialization function | @@ -410,6 +432,14 @@ int OnInit() PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE); SetIndexBuffer(1, DevelopingPOC_2); PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE); + SetIndexBuffer(2, DevelopingVAH_1); + PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE); + SetIndexBuffer(3, DevelopingVAH_2); + PlotIndexSetDouble(3, PLOT_EMPTY_VALUE, EMPTY_VALUE); + SetIndexBuffer(4, DevelopingVAL_1); + PlotIndexSetDouble(4, PLOT_EMPTY_VALUE, EMPTY_VALUE); + SetIndexBuffer(5, DevelopingVAL_2); + PlotIndexSetDouble(5, PLOT_EMPTY_VALUE, EMPTY_VALUE); ValueAreaPercentage_double = ValueAreaPercentage * 0.01; @@ -455,13 +485,24 @@ int OnCalculate(const int rates_total, } // New bars arrived? - if ((EnableDevelopingPOC) && (rates_total - prev_calculated > 1) && (CleanedUpOn != rates_total)) + if (((EnableDevelopingPOC) || (EnableDevelopingVAHVAL)) && (rates_total - prev_calculated > 1) && (CleanedUpOn != rates_total)) { // Initialize the indicator buffers. for (int i = prev_calculated; i < rates_total; i++) { DevelopingPOC_1[i] = EMPTY_VALUE; DevelopingPOC_2[i] = EMPTY_VALUE; + DevelopingVAH_1[i] = EMPTY_VALUE; + DevelopingVAH_2[i] = EMPTY_VALUE; + DevelopingVAL_1[i] = EMPTY_VALUE; + DevelopingVAL_2[i] = EMPTY_VALUE; + } + if ((prev_calculated == 0) && (Session == Rectangle)) // If prev_calculated got reset for some reason, reset the rectangles. + { + for (int i = mpr_total - 1; i >= 0 ; i--) + { + MPR_Array[i].ResetPrevTime0(); + } } CleanedUpOn = rates_total; // To prevent cleaning up the buffers again and again when the platform just starts. } @@ -1413,7 +1454,7 @@ bool ProcessSession(const int sessionstart, const int sessionend, const int i, C } } - if (EnableDevelopingPOC) CalculateDevelopingPOC(sessionstart, sessionend, rectangle); // Developing POC if necessary. + if ((EnableDevelopingPOC) || (EnableDevelopingVAHVAL)) CalculateDevelopingPOCVAHVAL(sessionstart, sessionend, rectangle); // Developing POC if necessary. double TotalTPOdouble = TotalTPO; // Calculate amount of TPO's in the Value Area. @@ -2404,12 +2445,16 @@ void OnTimer() { ObjectCleanup(); // Delete everything to make sure there are no leftover sessions behind the screen. if (Session == Intraday) FirstRunDone = false; // Turn off because FirstRunDone should be false for Intraday sessions to draw properly in the past. - if (EnableDevelopingPOC) + if ((EnableDevelopingPOC) || (EnableDevelopingVAHVAL)) { for (int i = 0; i < Bars; i++) // Clean indicator buffers. { DevelopingPOC_1[i] = EMPTY_VALUE; DevelopingPOC_2[i] = EMPTY_VALUE; + DevelopingVAH_1[i] = EMPTY_VALUE; + DevelopingVAH_2[i] = EMPTY_VALUE; + DevelopingVAL_1[i] = EMPTY_VALUE; + DevelopingVAL_2[i] = EMPTY_VALUE; } } } @@ -2433,7 +2478,7 @@ void CheckRectangles() { ObjectCleanup(MPR_Array[i].name + "_"); // Buffer cleanup for the Developing POC. - if (EnableDevelopingPOC) + if ((EnableDevelopingPOC) || (EnableDevelopingVAHVAL)) { int sessionstart = iBarShift(Symbol(), Period(), MPR_Array[i].RectangleTimeMin, true); int sessionend = iBarShift(Symbol(), Period(), MPR_Array[i].RectangleTimeMax, true); @@ -2443,6 +2488,10 @@ void CheckRectangles() { DevelopingPOC_1[j] = EMPTY_VALUE; DevelopingPOC_2[j] = EMPTY_VALUE; + DevelopingVAH_1[j] = EMPTY_VALUE; + DevelopingVAH_2[j] = EMPTY_VALUE; + DevelopingVAL_1[j] = EMPTY_VALUE; + DevelopingVAL_2[j] = EMPTY_VALUE; } } delete MPR_Array[i]; @@ -2596,7 +2645,7 @@ void CRectangleMP::Process(const int i) } // Buffer cleanup for the Developing POC. Should be run only for a changed rectangle, which isn't brand new. - if ((EnableDevelopingPOC) && (rectangle_changed) && (RectangleTimeMax != D'01.01.1970')) + if (((EnableDevelopingPOC) || (EnableDevelopingVAHVAL)) && (rectangle_changed) && (RectangleTimeMax != D'01.01.1970')) { int sessionstart = iBarShift(Symbol(), Period(), RectangleTimeMin, true); int sessionend = iBarShift(Symbol(), Period(), RectangleTimeMax, true); @@ -2606,7 +2655,12 @@ void CRectangleMP::Process(const int i) { DevelopingPOC_1[j] = EMPTY_VALUE; DevelopingPOC_2[j] = EMPTY_VALUE; + DevelopingVAH_1[j] = EMPTY_VALUE; + DevelopingVAH_2[j] = EMPTY_VALUE; + DevelopingVAL_1[j] = EMPTY_VALUE; + DevelopingVAL_2[j] = EMPTY_VALUE; } + prev_Time0 = 0; } RectangleTimeMax = MathMax(t1, t2); @@ -2719,6 +2773,11 @@ void CRectangleMP::Process(const int i) Number = i; } +void CRectangleMP::ResetPrevTime0() +{ + prev_Time0 = 0; +} + void PutSinglePrintMark(const double price, const int sessionstart, const string rectangle_prefix) { int t1 = sessionstart + 1, t2 = sessionstart; @@ -2879,13 +2938,13 @@ void RedrawLastSession() //+----------------------------------------------------------------------------------+ //| Go through all prices on all N session bars from 1st to kth bar, where k = 1..N. | //+----------------------------------------------------------------------------------+ -void CalculateDevelopingPOC(int sessionstart, int sessionend, CRectangleMP* rectangle = NULL) +void CalculateDevelopingPOCVAHVAL(int sessionstart, int sessionend, CRectangleMP* rectangle = NULL) { // Cycle through all possible end bars to calculate the Developing POC. for (int max_bar = sessionstart; max_bar >= sessionend; max_bar--) { if (((DevelopingPOC_1[max_bar] != EMPTY_VALUE) || (DevelopingPOC_2[max_bar] != EMPTY_VALUE)) && (max_bar > 1)) continue; // One of the buffers already filled and it isn't/wasn't the latest bar - skip. Valid only for non-Rectangle sessions. - + // Determine the local price minimum and maximum. double LocalMin = Low[ iLowest(Symbol(), Period(), MODE_LOW, sessionstart - max_bar + 1, max_bar)]; double LocalMax = High[iHighest(Symbol(), Period(), MODE_HIGH, sessionstart - max_bar + 1, max_bar)]; @@ -2900,7 +2959,15 @@ void CalculateDevelopingPOC(int sessionstart, int sessionend, CRectangleMP* rect double DistanceToCenter = DBL_MAX; // Reset the distance because each piece of the Developing POC should be using its own. int DevMaxRange = 0; // Maximum range for the Developing POC. double PriceOfMaxRange = EMPTY_VALUE; - + + // Will be necessary for Developing VAH/VAL calculation. + int TotalTPO = 0; // Total amount of dots (TPO's). + int TPOperPrice[]; + // Possible price levels if multiplied to integer. + int max = (int)MathRound((LocalMax - LocalMin) / onetick + 2); // + 2 because further we will be possibly checking array at LocalMax + 1. + ArrayResize(TPOperPrice, max); + ArrayInitialize(TPOperPrice, 0); + // Cycle by price inside the local boundaries: for (double price = LocalMax; price >= LocalMin; price -= onetick) { @@ -2920,45 +2987,94 @@ void CalculateDevelopingPOC(int sessionstart, int sessionend, CRectangleMP* rect PriceOfMaxRange = price; DistanceToCenter = MathAbs(price - (LocalMin + (LocalMax - LocalMin) / 2)); } + // Remember the number of encountered bars for this price for Developing VAH/VAL. + int index = (int)MathRound((price - LocalMin) / onetick); + TPOperPrice[index]++; + TotalTPO++; range++; } } } - // Both buffer are empty: - if ((DevelopingPOC_1[max_bar + 1] == EMPTY_VALUE) && (DevelopingPOC_2[max_bar + 1] == EMPTY_VALUE)) - { - DevelopingPOC_1[max_bar] = PriceOfMaxRange; // Starting with the first one. - DevelopingPOC_2[max_bar] = EMPTY_VALUE; // The second is initialized to an empty value. - } - // Buffer #1 already had a value, - else if (DevelopingPOC_1[max_bar + 1] != EMPTY_VALUE) + if (EnableDevelopingVAHVAL) { - // and it is different from what we get now. - if (DevelopingPOC_1[max_bar + 1] != PriceOfMaxRange) - { - DevelopingPOC_2[max_bar] = PriceOfMaxRange; // Use new buffer to get an interrupted shift of lines. - DevelopingPOC_1[max_bar] = EMPTY_VALUE; - } - else // and it is the same price: + double TotalTPOdouble = TotalTPO; + // Calculate amount of TPO's in the Value Area. + int ValueControlTPO = (int)MathRound(TotalTPOdouble * ValueAreaPercentage_double); + // Start with the TPO's of the Median. + int index = (int)((PriceOfMaxRange - LocalMin) / onetick); + if (index < 0) continue; // Data wasn't available yet. + int TPOcount = TPOperPrice[index]; + + // Go through the price levels above and below median adding the biggest to TPO count until the 70% of TPOs are inside the Value Area. + int up_offset = 1; + int down_offset = 1; + while (TPOcount < ValueControlTPO) { - DevelopingPOC_1[max_bar] = PriceOfMaxRange; // Use the same buffer. - DevelopingPOC_2[max_bar] = EMPTY_VALUE; + double abovePrice = PriceOfMaxRange + up_offset * onetick; + double belowPrice = PriceOfMaxRange - down_offset * onetick; + // If belowPrice is out of the session's range then we should add only abovePrice's TPO's, and vice versa. + index = (int)MathRound((abovePrice - LocalMin) / onetick); + int index2 = (int)MathRound((belowPrice - LocalMin) / onetick); + if (((belowPrice < LocalMin) || (TPOperPrice[index] >= TPOperPrice[index2])) && (abovePrice <= LocalMax)) + { + TPOcount += TPOperPrice[index]; + up_offset++; + } + else if (belowPrice >= LocalMin) + { + TPOcount += TPOperPrice[index2]; + down_offset++; + } + // Cannot proceed - too few data points. + else if (TPOcount < ValueControlTPO) + { + break; + } } + DistributeBetweenTwoBuffers(DevelopingVAH_1, DevelopingVAH_2, max_bar, PriceOfMaxRange + up_offset * onetick); + DistributeBetweenTwoBuffers(DevelopingVAL_1, DevelopingVAL_2, max_bar, PriceOfMaxRange - down_offset * onetick + onetick); } - // Buffer #2 already had a value, - else + if (EnableDevelopingPOC) DistributeBetweenTwoBuffers(DevelopingPOC_1, DevelopingPOC_2, max_bar, PriceOfMaxRange); + } +} + +// Used for Developing POC, VAH, and VAL lines. +void DistributeBetweenTwoBuffers(double &buff1[], double &buff2[], int bar, double price) +{ + // Both buffer are empty: + if ((buff1[bar + 1] == EMPTY_VALUE) && (buff2[bar + 1] == EMPTY_VALUE)) + { + buff1[bar] = price; // Starting with the first one. + buff2[bar] = EMPTY_VALUE; // The second is initialized to an empty value. + } + // Buffer #1 already had a value, + else if (buff1[bar + 1] != EMPTY_VALUE) + { + // and it is different from what we get now. + if (buff1[bar + 1] != price) { - // and it is different from what we get now. - if (DevelopingPOC_2[max_bar + 1] != PriceOfMaxRange) - { - DevelopingPOC_1[max_bar] = PriceOfMaxRange; // Use new buffer to get an interrupted shift of lines. - DevelopingPOC_2[max_bar] = EMPTY_VALUE; - } - else // and it is the same price: - { - DevelopingPOC_2[max_bar] = PriceOfMaxRange; // Use the same buffer. - DevelopingPOC_1[max_bar] = EMPTY_VALUE; - } + buff2[bar] = price; // Use new buffer to get an interrupted shift of lines. + buff1[bar] = EMPTY_VALUE; + } + else // and it is the same price: + { + buff1[bar] = price; // Use the same buffer. + buff2[bar] = EMPTY_VALUE; + } + } + // Buffer #2 already had a value, + else + { + // and it is different from what we get now. + if (buff2[bar + 1] != price) + { + buff1[bar] = price; // Use new buffer to get an interrupted shift of lines. + buff2[bar] = EMPTY_VALUE; + } + else // and it is the same price: + { + buff2[bar] = price; // Use the same buffer. + buff1[bar] = EMPTY_VALUE; } } } diff --git a/MarketProfile.mq5 b/MarketProfile.mq5 index 0acb6bb..b3b12bb 100644 --- a/MarketProfile.mq5 +++ b/MarketProfile.mq5 @@ -1,11 +1,11 @@ //+------------------------------------------------------------------+ //| MarketProfile.mq5 | -//| Copyright © 2010-2023, EarnForex.com | +//| Copyright © 2010-2024, EarnForex.com | //| https://www.earnforex.com/ | //+------------------------------------------------------------------+ #property copyright "EarnForex.com" #property link "https://www.earnforex.com/metatrader-indicators/MarketProfile/" -#property version "1.22" +#property version "1.23" #property description "Displays the Market Profile indicator for intraday, daily, weekly, or monthly trading sessions." #property description "Daily - should be attached to M5-M30 timeframes. M30 is recommended." @@ -18,9 +18,9 @@ // Rectangle session - a rectangle's name should start with 'MPR' and must not contain an underscore ('_'). //+------------------------------------------------------------------+ #property indicator_chart_window -// Two buffers are used for the Developing POC display because a single buffer wouldn't support an interrupting line. -#property indicator_plots 2 -#property indicator_buffers 2 +// Two buffers are used for the Developing POC and Developing VAH/VAL display because a single buffer wouldn't support an interrupting line. +#property indicator_plots 6 +#property indicator_buffers 6 #property indicator_color1 clrGreen #property indicator_color2 clrGreen #property indicator_width1 5 @@ -31,6 +31,26 @@ #property indicator_style2 STYLE_SOLID #property indicator_label1 "Developing POC" #property indicator_label2 "Developing POC" +#property indicator_color3 clrGoldenrod +#property indicator_color4 clrGoldenrod +#property indicator_width3 5 +#property indicator_width4 5 +#property indicator_type3 DRAW_LINE +#property indicator_type4 DRAW_LINE +#property indicator_style3 STYLE_SOLID +#property indicator_style4 STYLE_SOLID +#property indicator_label3 "Developing VAH" +#property indicator_label4 "Developing VAH" +#property indicator_color5 clrSalmon +#property indicator_color6 clrSalmon +#property indicator_width5 5 +#property indicator_width6 5 +#property indicator_type5 DRAW_LINE +#property indicator_type6 DRAW_LINE +#property indicator_style5 STYLE_SOLID +#property indicator_style6 STYLE_SOLID +#property indicator_label5 "Developing VAL" +#property indicator_label6 "Developing VAL" enum color_scheme { @@ -110,8 +130,9 @@ input session_period Session = Daily; input datetime StartFromDate = __DATE__; // StartFromDate: lower priority. input bool StartFromCurrentSession = true; // StartFromCurrentSession: higher priority. input int SessionsToCount = 2; // SessionsToCount: Number of sessions to count Market Profile. -input bool SeamlessScrollingMode = false; // SeamlessScrollingMode: show sessions on current screen. -input bool EnableDevelopingPOC = false; // Enable Developing POC. +input bool SeamlessScrollingMode = false; // SeamlessScrollingMode: Show sessions on current screen. +input bool EnableDevelopingPOC = false; // Enable Developing POC +input bool EnableDevelopingVAHVAL = false; // Enable Developing VAH/VAL input int ValueAreaPercentage = 70; // ValueAreaPercentage: Percentage of TPO's inside Value Area. input group "Colors and looks" @@ -261,12 +282,13 @@ public: CRectangleMP(string); ~CRectangleMP(void) {}; void Process(int, const double& High[], const double& Low[], const datetime& Time[], const int rates_total); + void ResetPrevTime0(); }; CRectangleMP* MPR_Array[]; int mpr_total = 0; uint LastRecalculationTime = 0; -double DevelopingPOC_1[], DevelopingPOC_2[]; // Indicator buffers for Developing POC. +double DevelopingPOC_1[], DevelopingPOC_2[], DevelopingVAH_1[], DevelopingVAH_2[], DevelopingVAL_1[], DevelopingVAL_2[]; // Indicator buffers for Developing POC and VAH/VAL. //+------------------------------------------------------------------+ //| Custom indicator initialization function | @@ -362,6 +384,7 @@ int OnInit() else { string s = DoubleToString(quote, _Digits); + StringReplace(s, "-", ""); // Remove the minus sign for a negative price. int total_digits = StringLen(s); // If there is a dot in a quote. if (StringFind(s, ".") != -1) total_digits--; // Decrease the count of digits by one. @@ -411,6 +434,18 @@ int OnInit() SetIndexBuffer(1, DevelopingPOC_2, INDICATOR_DATA); ArraySetAsSeries(DevelopingPOC_2, true); PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE); + SetIndexBuffer(2, DevelopingVAH_1, INDICATOR_DATA); + PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE); + ArraySetAsSeries(DevelopingVAH_1, true); + SetIndexBuffer(3, DevelopingVAH_2, INDICATOR_DATA); + PlotIndexSetDouble(3, PLOT_EMPTY_VALUE, EMPTY_VALUE); + ArraySetAsSeries(DevelopingVAH_2, true); + SetIndexBuffer(4, DevelopingVAL_1, INDICATOR_DATA); + PlotIndexSetDouble(4, PLOT_EMPTY_VALUE, EMPTY_VALUE); + ArraySetAsSeries(DevelopingVAL_1, true); + SetIndexBuffer(5, DevelopingVAL_2, INDICATOR_DATA); + PlotIndexSetDouble(5, PLOT_EMPTY_VALUE, EMPTY_VALUE); + ArraySetAsSeries(DevelopingVAL_2, true); ValueAreaPercentage_double = ValueAreaPercentage * 0.01; @@ -456,13 +491,24 @@ int OnCalculate(const int rates_total, } // New bars arrived? - if ((EnableDevelopingPOC) && (rates_total - prev_calculated > 1) && (CleanedUpOn != rates_total)) + if (((EnableDevelopingPOC) || (EnableDevelopingVAHVAL)) && (rates_total - prev_calculated > 1) && (CleanedUpOn != rates_total)) { // Initialize the indicator buffers. for (int i = prev_calculated; i < rates_total; i++) { DevelopingPOC_1[i] = EMPTY_VALUE; DevelopingPOC_2[i] = EMPTY_VALUE; + DevelopingVAH_1[i] = EMPTY_VALUE; + DevelopingVAH_2[i] = EMPTY_VALUE; + DevelopingVAL_1[i] = EMPTY_VALUE; + DevelopingVAL_2[i] = EMPTY_VALUE; + } + if ((prev_calculated == 0) && (Session == Rectangle)) // If prev_calculated got reset for some reason, reset the rectangles. + { + for (int i = mpr_total - 1; i >= 0 ; i--) + { + MPR_Array[i].ResetPrevTime0(); + } } CleanedUpOn = rates_total; // To prevent cleaning up the buffers again and again when the platform just starts. } @@ -1437,7 +1483,7 @@ bool ProcessSession(const int sessionstart, const int sessionend, const int i, c } } - if (EnableDevelopingPOC) CalculateDevelopingPOC(sessionstart, sessionend, High, Low, rectangle); // Developing POC if necessary. + if ((EnableDevelopingPOC) || (EnableDevelopingVAHVAL)) CalculateDevelopingPOCVAHVAL(sessionstart, sessionend, High, Low, rectangle); // Developing POC if necessary. // Calculate amount of TPO's in the Value Area. int ValueControlTPO = (int)((double)TotalTPO * ValueAreaPercentage_double); @@ -2519,12 +2565,16 @@ void OnTimer() { ObjectCleanup(); // Delete everything to make sure there are no leftover sessions behind the screen. if (Session == Intraday) FirstRunDone = false; // Turn off because FirstRunDone should be false for Intraday sessions to draw properly in the past. - if (EnableDevelopingPOC) + if ((EnableDevelopingPOC) || (EnableDevelopingVAHVAL)) { for (int i = 0; i < Bars(Symbol(), Period()); i++) // Clean indicator buffers. { DevelopingPOC_1[i] = EMPTY_VALUE; DevelopingPOC_2[i] = EMPTY_VALUE; + DevelopingVAH_1[i] = EMPTY_VALUE; + DevelopingVAH_2[i] = EMPTY_VALUE; + DevelopingVAL_1[i] = EMPTY_VALUE; + DevelopingVAL_2[i] = EMPTY_VALUE; } } } @@ -2549,7 +2599,7 @@ void CheckRectangles(const double& High[], const double& Low[], const datetime& { ObjectCleanup(MPR_Array[i].name + "_"); // Buffer cleanup for the Developing POC. - if (EnableDevelopingPOC) + if ((EnableDevelopingPOC) || (EnableDevelopingVAHVAL)) { int sessionstart = iBarShift(Symbol(), Period(), MPR_Array[i].RectangleTimeMin, true); int sessionend = iBarShift(Symbol(), Period(), MPR_Array[i].RectangleTimeMax, true); @@ -2560,6 +2610,10 @@ void CheckRectangles(const double& High[], const double& Low[], const datetime& { DevelopingPOC_1[j] = EMPTY_VALUE; DevelopingPOC_2[j] = EMPTY_VALUE; + DevelopingVAH_1[j] = EMPTY_VALUE; + DevelopingVAH_2[j] = EMPTY_VALUE; + DevelopingVAL_1[j] = EMPTY_VALUE; + DevelopingVAL_2[j] = EMPTY_VALUE; } } delete MPR_Array[i]; @@ -2726,7 +2780,7 @@ void CRectangleMP::Process(const int i, const double& High[], const double& Low[ } // Buffer cleanup for the Developing POC. Should be run only for a changed rectangle, which isn't brand new. - if ((EnableDevelopingPOC) && (rectangle_changed) && (RectangleTimeMax != D'01.01.1970')) + if (((EnableDevelopingPOC) || (EnableDevelopingVAHVAL)) && (rectangle_changed) && (RectangleTimeMax != D'01.01.1970')) { int local_sessionstart = iBarShift(Symbol(), Period(), RectangleTimeMin, true); int local_sessionend = iBarShift(Symbol(), Period(), RectangleTimeMax, true); @@ -2737,7 +2791,12 @@ void CRectangleMP::Process(const int i, const double& High[], const double& Low[ { DevelopingPOC_1[j] = EMPTY_VALUE; DevelopingPOC_2[j] = EMPTY_VALUE; + DevelopingVAH_1[j] = EMPTY_VALUE; + DevelopingVAH_2[j] = EMPTY_VALUE; + DevelopingVAL_1[j] = EMPTY_VALUE; + DevelopingVAL_2[j] = EMPTY_VALUE; } + prev_Time0 = 0; } RectangleTimeMax = MathMax(t1, t2); @@ -2845,6 +2904,11 @@ void CRectangleMP::Process(const int i, const double& High[], const double& Low[ Number = i; } +void CRectangleMP::ResetPrevTime0() +{ + prev_Time0 = 0; +} + void PutSinglePrintMark(const double price, const int sessionstart, const string rectangle_prefix, const datetime& Time[]) { int t1 = sessionstart + 1, t2 = sessionstart; @@ -3006,7 +3070,7 @@ void RedrawLastSession(const double& High[], const double& Low[], const datetime //+----------------------------------------------------------------------------------+ //| Go through all prices on all N session bars from 1st to kth bar, where k = 1..N. | //+----------------------------------------------------------------------------------+ -void CalculateDevelopingPOC(const int sessionstart, const int sessionend, const double& High[], const double& Low[], CRectangleMP* rectangle = NULL) +void CalculateDevelopingPOCVAHVAL(const int sessionstart, const int sessionend, const double& High[], const double& Low[], CRectangleMP* rectangle = NULL) { // Cycle through all possible end bars to calculate the Developing POC. for (int max_bar = sessionstart; max_bar >= sessionend; max_bar--) @@ -3027,7 +3091,15 @@ void CalculateDevelopingPOC(const int sessionstart, const int sessionend, const double DistanceToCenter = DBL_MAX; // Reset the distance because each piece of the Developing POC should be using its own. int DevMaxRange = 0; // Maximum range for the Developing POC. double PriceOfMaxRange = EMPTY_VALUE; - + + // Will be necessary for Developing VAH/VAL calculation. + int TotalTPO = 0; // Total amount of dots (TPO's). + int TPOperPrice[]; + // Possible price levels if multiplied to integer. + int max = (int)MathRound((LocalMax - LocalMin) / onetick + 2); // + 2 because further we will be possibly checking array at LocalMax + 1. + ArrayResize(TPOperPrice, max); + ArrayInitialize(TPOperPrice, 0); + // Cycle by price inside the local boundaries: for (double price = LocalMax; price >= LocalMin; price -= onetick) { @@ -3047,49 +3119,103 @@ void CalculateDevelopingPOC(const int sessionstart, const int sessionend, const PriceOfMaxRange = price; DistanceToCenter = MathAbs(price - (LocalMin + (LocalMax - LocalMin) / 2)); } + // Remember the number of encountered bars for this price for Developing VAH/VAL. + int index = (int)MathRound((price - LocalMin) / onetick); + TPOperPrice[index]++; + TotalTPO++; range++; } } } - // Both buffer are empty: - if ((DevelopingPOC_1[max_bar + 1] == EMPTY_VALUE) && (DevelopingPOC_2[max_bar + 1] == EMPTY_VALUE)) - { - DevelopingPOC_1[max_bar] = PriceOfMaxRange; // Starting with the first one. - DevelopingPOC_2[max_bar] = EMPTY_VALUE; // The second is initialized to an empty value. - } - // Buffer #1 already had a value, - else if (DevelopingPOC_1[max_bar + 1] != EMPTY_VALUE) + if (EnableDevelopingVAHVAL) { - // and it is different from what we get now. - if (DevelopingPOC_1[max_bar + 1] != PriceOfMaxRange) - { - DevelopingPOC_2[max_bar] = PriceOfMaxRange; // Use new buffer to get an interrupted shift of lines. - DevelopingPOC_1[max_bar] = EMPTY_VALUE; - } - else // and it is the same price: + double TotalTPOdouble = TotalTPO; + // Calculate amount of TPO's in the Value Area. + int ValueControlTPO = (int)MathRound(TotalTPOdouble * ValueAreaPercentage_double); + // Start with the TPO's of the Median. + int index = (int)((PriceOfMaxRange - LocalMin) / onetick); + if (index < 0) continue; // Data wasn't available yet. + int TPOcount = TPOperPrice[index]; + + // Go through the price levels above and below median adding the biggest to TPO count until the 70% of TPOs are inside the Value Area. + int up_offset = 1; + int down_offset = 1; + while (TPOcount < ValueControlTPO) { - DevelopingPOC_1[max_bar] = PriceOfMaxRange; // Use the same buffer. - DevelopingPOC_2[max_bar] = EMPTY_VALUE; + double abovePrice = PriceOfMaxRange + up_offset * onetick; + double belowPrice = PriceOfMaxRange - down_offset * onetick; + // If belowPrice is out of the session's range then we should add only abovePrice's TPO's, and vice versa. + index = (int)MathRound((abovePrice - LocalMin) / onetick); + int index2 = (int)MathRound((belowPrice - LocalMin) / onetick); + if (((belowPrice < LocalMin) || (TPOperPrice[index] >= TPOperPrice[index2])) && (abovePrice <= LocalMax)) + { + TPOcount += TPOperPrice[index]; + up_offset++; + } + else if (belowPrice >= LocalMin) + { + TPOcount += TPOperPrice[index2]; + down_offset++; + } + // Cannot proceed - too few data points. + else if (TPOcount < ValueControlTPO) + { + break; + } } + DistributeBetweenTwoBuffers(DevelopingVAH_1, DevelopingVAH_2, max_bar, PriceOfMaxRange + up_offset * onetick); + DistributeBetweenTwoBuffers(DevelopingVAL_1, DevelopingVAL_2, max_bar, PriceOfMaxRange - down_offset * onetick + onetick); } - // Buffer #2 already had a value, - else + if (EnableDevelopingPOC) DistributeBetweenTwoBuffers(DevelopingPOC_1, DevelopingPOC_2, max_bar, PriceOfMaxRange); + } +} + +// Used for Developing POC, VAH, and VAL lines. +void DistributeBetweenTwoBuffers(double &buff1[], double &buff2[], int bar, double price) +{ + // Both buffer are empty: + if ((buff1[bar + 1] == EMPTY_VALUE) && (buff2[bar + 1] == EMPTY_VALUE)) + { + buff1[bar] = price; // Starting with the first one. + buff2[bar] = EMPTY_VALUE; // The second is initialized to an empty value. + } + // Buffer #1 already had a value, + else if (buff1[bar + 1] != EMPTY_VALUE) + { + // and it is different from what we get now. + if (buff1[bar + 1] != price) { - // and it is different from what we get now. - if (DevelopingPOC_2[max_bar + 1] != PriceOfMaxRange) - { - DevelopingPOC_1[max_bar] = PriceOfMaxRange; // Use new buffer to get an interrupted shift of lines. - DevelopingPOC_2[max_bar] = EMPTY_VALUE; - } - else // and it is the same price: - { - DevelopingPOC_2[max_bar] = PriceOfMaxRange; // Use the same buffer. - DevelopingPOC_1[max_bar] = EMPTY_VALUE; - } + + + + buff2[bar] = price; // Use new buffer to get an interrupted shift of lines. + buff1[bar] = EMPTY_VALUE; + } + else // and it is the same price: + { + buff1[bar] = price; // Use the same buffer. + buff2[bar] = EMPTY_VALUE; + + } + } + // Buffer #2 already had a value, + else + { + // and it is different from what we get now. + if (buff2[bar + 1] != price) + { + buff1[bar] = price; // Use new buffer to get an interrupted shift of lines. + buff2[bar] = EMPTY_VALUE; + } + else // and it is the same price: + { + buff2[bar] = price; // Use the same buffer. + buff1[bar] = EMPTY_VALUE; } } } + //+------------------------------------------------------------------+ //| For keystroke processing in Rectangle sessions. | //+------------------------------------------------------------------+