Category Other  Published on 21/11/2022

Free Volume Profile v2.0

Description

All core features of TPO Profile v2.0 but in VOLUME
It also has the features of Order Flow Ticks v2.0

Last Update -> 12/12/2024
Previous Downloads: 8038+

What's new in v2.0?

  • Added Params Panel for quickly switch between settings (volume modes, row height, interval, etc) as well as more user-friendly.
  • Refactor to only use Colors API
  • Should work with Mac OS users.
  • .NET 6.0+ is Required

==================================================
Normal/Gradient Mode = Volume Profile with Fixed/Gradient Color
Buy vs Sell Mode = The name explains itself
Delta Mode = Volume Delta Profile
Normal+Delta Mode = Volume + Delta (Profile)

The Volume Calculation(in Bars Volume Source)
is exported, with adaptations, from the BEST VP I have see/used,
of FXcoder's (VP 10.2.1), author of the famous (Volume Profile + Range v6.0)
a BIG THANKS to HIM!

All parameters are self-explanatory.

Update:
12/12/2024 - Version 2.0
16/11/2022 - A "new" and correct way of segmentation has been added, where there is no more "repaint/flicker" when a new high was formed in Live Market.
The old wrong segmentation has been replaced by this one.

=================================================================


/*
--------------------------------------------------------------------------------------------------------------------------------
                        Volume Profile v2.0

All core features of TPO Profile but in VOLUME
It also has the features of Order Flow Ticks

=== Volume Modes ===
*Normal/Gradient Mode = Volume Profile with Fixed/Gradient Color
*Buy vs Sell Mode = The name explains itself
*Delta Mode = Volume Delta Profile
*Normal+Delta Mode = Volume + Delta

The Volume Calculation(in Bars Volume Source)
is exported, with adaptations, from the BEST VP I have see/used for MT4/MT5,
of Russian FXcoder's https://gitlab.com/fxcoder-mql/vp (VP 10.1), author of the famous (Volume Profile + Range v6.0)
a BIG THANKS to HIM!

All parameters are self-explanatory.

.NET 6.0+ is Required

What's new in v2.0?
-Added Params Panel for quickly switch between settings (volume modes, row height, interval, etc) and most importantly, more user-friendly.
-Refactor to only use Colors API.
-Should work with Mac OS users.


AUTHOR: srlcarlg

== DON"T BE an ASSHOLE SELLING this FREE and OPEN-SOURCE indicator ==
----------------------------------------------------------------------------------------------------------------------------
*/

using System;
using System.Globalization;
using System.Collections.Generic;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Internals;
using static cAlgo.FreeVolumeProfileV20;

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class FreeVolumeProfileV20 : Indicator
    {
        public enum PanelAlignData
        {
            Top_Left,
            Top_Center,
            Top_Right,
            Center_Left,
            Center_Right,
            Bottom_Left,
            Bottom_Center,
            Bottom_Right,
        }
        [Parameter("Panel Position:", DefaultValue = PanelAlignData.Bottom_Right, Group = "==== Volume Profile v2.0 ====")]
        public PanelAlignData PanelAlignInput { get; set; }

        public enum VolumeSourceData
        {
            Ticks,
            Bars,
        }
        [Parameter("Volume Source:", DefaultValue = VolumeSourceData.Bars, Group = "==== Volume Profile v2.0 ====")]
        public VolumeSourceData VolumeSourceInput { get; set; }


        public enum ConfigRowData
        {
            Predefined,
            Custom,
        }
        [Parameter("Config:", DefaultValue = ConfigRowData.Predefined, Group = "==== Config ====")]
        public ConfigRowData ConfigRowInput { get; set; }

        [Parameter("Custom Interval:", DefaultValue = "Daily", Group = "==== Config ====")]
        public TimeFrame CustomInterval { get; set; }

        [Parameter("Custom Row Height:", DefaultValue = 1, MinValue = 0.2, Group = "==== Config ====")]
        public double CustomHeight { get; set; }


        [Parameter("[Bars]Source:", DefaultValue = "Minute", Group = "==== Volume Source ====")]
        public TimeFrame BarsVOLSource_TF { get; set; }

        public enum DistributionData
        {
            OHLC,
            High,
            Low,
            Close,
            Uniform_Distribution,
            Uniform_Presence,
            Parabolic_Distribution,
            Triangular_Distribution,
        }
        [Parameter("[Bars]Distribution:", DefaultValue = DistributionData.OHLC, Group = "==== Volume Source ====")]
        public DistributionData DistributionInput { get; set; }

        public enum LoadFromData
        {
            According_to_Lookback,
            Today,
            Yesterday,
            One_Week,
            Custom
        }
        [Parameter("[Ticks]Load From:", DefaultValue = LoadFromData.Today, Group = "==== Volume Source ====")]
        public LoadFromData LoadFromInput { get; set; }

        [Parameter("[Ticks]Custom (dd/mm/yyyy):", DefaultValue = "00/00/0000", Group = "==== Volume Source ====")]
        public string StringDate { get; set; }


        public enum HistWidthData
        {
            _15,
            _30,
            _50,
            _70,
            _100
        }
        [Parameter("Histogram Width(%)", DefaultValue = HistWidthData._50, Group = "==== Histogram Settings ====")]
        public HistWidthData HistWidthInput { get; set; }

        public enum HistSideData
        {
            Left,
            Right,
        }
        [Parameter("Histogram Side", DefaultValue = HistSideData.Left, Group = "==== Histogram Settings ====")]
        public HistSideData HistogramSideInput { get; set; }

        [Parameter("Fill Histogram?", DefaultValue = true, Group = "==== Histogram Settings ====")]
        public bool FillHist { get; set; }

        [Parameter("[Gradient] Opacity:", DefaultValue = 60, MinValue = 5, MaxValue = 100, Group = "==== Histogram Settings ====")]
        public int OpacityHistInput { get; set; }


        [Parameter("Extended VAs?", DefaultValue = false, Group = "==== Other settings ====")]
        public bool ExtendVA { get; set; }

        [Parameter("Show OHLC Bar?", DefaultValue = false, Group = "==== Other settings ====")]
        public bool ShowOHLC { get; set; }

        [Parameter("Show Results(%)?", DefaultValue = true, Group = "==== Other settings ====")]
        public bool ShowResults { get; set; }

        [Parameter("Font Size Results:", DefaultValue = 10, MinValue = 1, MaxValue = 80, Group = "==== Other settings ====")]
        public int FontSizeResults { get; set; }


        [Parameter("Normal Color:", DefaultValue = "#99808080", Group = "==== Colors Histogram ====")]
        public Color HistColor  { get; set; }

        [Parameter("Normal Color inside VA:", DefaultValue = "#CC00BFFF", Group = "==== Colors Histogram ====")]
        public Color HistColorVA  { get; set; }

        [Parameter("Gradient Color Min. Vol:", DefaultValue = "RoyalBlue", Group = "==== Colors Histogram ====")]
        public Color ColorGrandient_Min { get; set; }

        [Parameter("Gradient Color Max. Vol:", DefaultValue = "OrangeRed", Group = "==== Colors Histogram ====")]
        public Color ColorGrandient_Max { get; set; }

        [Parameter("Color Buy:", DefaultValue = "#9900BFFF", Group = "==== Colors Histogram ====")]
        public Color BuyColor  { get; set; }

        [Parameter("Color Sell:", DefaultValue = "#99DC143C", Group = "==== Colors Histogram ====")]
        public Color SellColor  { get; set; }

        [Parameter("OHLC Bar Color:", DefaultValue = "Gray", Group = "==== Colors Histogram ====")]
        public Color ColorOHLC { get; set; }


        [Parameter("Color POC:", DefaultValue = "D0FFD700", Group = "==== Point of Control ====")]
        public Color ColorPOC { get; set; }

        [Parameter("LineStyle POC:", DefaultValue = LineStyle.Lines, Group = "==== Point of Control ====")]
        public LineStyle LineStylePOC { get; set; }

        [Parameter("Thickness POC:", DefaultValue = 1, MinValue = 1, MaxValue = 5, Group = "==== Point of Control ====")]
        public int ThicknessPOC { get; set; }


        [Parameter("Color VA:", DefaultValue = "#19F0F8FF", Group = "==== Value Area ====")]
        public Color VAColor  { get; set; }

        [Parameter("Color VAH:", DefaultValue = "PowderBlue", Group = "==== Value Area ====")]
        public Color ColorVAH { get; set; }

        [Parameter("Color VAL:", DefaultValue = "PowderBlue", Group = "==== Value Area ====")]
        public Color ColorVAL { get; set; }

        [Parameter("Opacity VA", DefaultValue = 10, MinValue = 5, MaxValue = 100, Group = "==== Value Area ====")]
        public int OpacityVA { get; set; }

        [Parameter("LineStyle VA:", DefaultValue = LineStyle.LinesDots, Group = "==== Value Area ====")]
        public LineStyle LineStyleVA { get; set; }

        [Parameter("Thickness VA:", DefaultValue = 1, MinValue = 1, MaxValue = 5, Group = "==== Value Area ====")]
        public int ThicknessVA { get; set; }


        [Parameter("Developed for cTrader/C#", DefaultValue = "by srlcarlg", Group = "==== Credits ====")]
        public string Credits { get; set; }

        private readonly VerticalAlignment V_Align = VerticalAlignment.Top;
        private readonly HorizontalAlignment H_Align = HorizontalAlignment.Center;

        private List<double> Segments = new();
        private readonly IDictionary<double, double> VolumesRank = new Dictionary<double, double>();
        private readonly IDictionary<double, double> VolumesRank_Up = new Dictionary<double, double>();
        private readonly IDictionary<double, double> VolumesRank_Down = new Dictionary<double, double>();
        private readonly IDictionary<double, double> DeltaRank = new Dictionary<double, double>();

        private readonly List<ChartTrendLine> POCsLines = new();
        private readonly List<ChartTrendLine> VALines = new();

        private readonly IDictionary<int, ChartRectangle> RectanglesToColor = new Dictionary<int, ChartRectangle>();

        private DateTime FromDateTime;
        private TimeFrame LookBack_TF;
        private Bars LookBack_Bars;
        private Bars TicksOHLC;
        private Bars VOL_Bars;

        private double HeightPips = 4;
        private double rowHeight = 0;
        private double drawHeight = 0;
        private double prevPrice;
        private double[] priceVA_LHP = { 0, 0, 0 };

        private bool isLive = false;
        private bool Wrong = false;
        private bool configHasChanged = false;

        private int cleanedIndex;

        // Moved from cTrader Input to Params Panel
        public int Lookback { get; set; } = 5;
        public enum VolumeModeData
        {
            Normal,
            Gradient,
            Buy_Sell,
            Delta,
            Normal_Delta
        }
        public VolumeModeData VolumeModeInput { get; set; } = VolumeModeData.Gradient;
        public bool ShowVA { get; set; } = false;
        public bool KeepPOC { get; set; } = true;
        public bool ExtendPOC { get; set; } = false;

        // Params Panel
        private Border ParamBorder;
        public class IndicatorParams
        {
            public int LookBack { get; set; }
            public VolumeModeData VolMode { get; set; }
            public double RowHeight { get; set; }
            public TimeFrame Interval { get; set; }
            public bool KeepPOC { get; set; }
            public bool ExtendedPOC { get; set; }
            public bool ShowVA { get; set; }
        }

        private void AddHiddenButton(Panel panel, Color btnColor)
        {
            Button button = new()
            {
                Text = "VP",
                Padding = 0,
                Height = 22,
                Margin = 2,
                BackgroundColor = btnColor
            };
            button.Click += HiddenEvent;
            panel.AddChild(button);
        }
        private void HiddenEvent(ButtonClickEventArgs obj)
        {
            if (ParamBorder.IsVisible)
                ParamBorder.IsVisible = false;
            else
                ParamBorder.IsVisible = true;
        }

        protected override void Initialize()
        {
            // ========== Predefined Config ==========
            if (ConfigRowInput == ConfigRowData.Predefined && (Chart.TimeFrame >= TimeFrame.Minute && Chart.TimeFrame <= TimeFrame.Day3))
            {
                if (Chart.TimeFrame >= TimeFrame.Minute && Chart.TimeFrame <= TimeFrame.Minute4)
                {
                    if (Chart.TimeFrame == TimeFrame.Minute)
                        LookBack_TF = TimeFrame.Hour;
                    else if (Chart.TimeFrame == TimeFrame.Minute2)
                        LookBack_TF = TimeFrame.Hour2;
                    else if (Chart.TimeFrame <= TimeFrame.Minute4)
                        LookBack_TF = TimeFrame.Hour3;

                    SetHeightPips(0.5, 8);
                }
                else if (Chart.TimeFrame >= TimeFrame.Minute5 && Chart.TimeFrame <= TimeFrame.Minute10)
                {
                    if (Chart.TimeFrame == TimeFrame.Minute5)
                        LookBack_TF = TimeFrame.Hour4;
                    else if (Chart.TimeFrame == TimeFrame.Minute6)
                        LookBack_TF = TimeFrame.Hour6;
                    else if (Chart.TimeFrame <= TimeFrame.Minute8)
                        LookBack_TF = TimeFrame.Hour8;
                    else if (Chart.TimeFrame <= TimeFrame.Minute10)
                        LookBack_TF = TimeFrame.Hour12;

                    SetHeightPips(0.5, 25);
                }
                else if (Chart.TimeFrame >= TimeFrame.Minute15 && Chart.TimeFrame <= TimeFrame.Hour8)
                {
                    if (Chart.TimeFrame >= TimeFrame.Minute15 && Chart.TimeFrame <= TimeFrame.Hour) {
                        LookBack_TF = TimeFrame.Daily;
                        SetHeightPips(2, 50);
                    }
                    else if (Chart.TimeFrame <= TimeFrame.Hour8) {
                        LookBack_TF = TimeFrame.Weekly;
                        SetHeightPips(4, 140);
                    }
                }
                else if (Chart.TimeFrame >= TimeFrame.Hour12 && Chart.TimeFrame <= TimeFrame.Day3)
                {
                    LookBack_TF = TimeFrame.Monthly;
                    SetHeightPips(6, 220);
                }
            }
            else
            {
                string[] timeBased = { "Minute", "Hour", "Daily", "Day" };
                if (ConfigRowInput == ConfigRowData.Predefined)
                {
                    string Msg = "'Predefined Config' is designed only for Standard Timeframe (Minutes, Hours, Days) \n Weekly and Monthly is not currently supported \n\n use 'Custom Config' to others Chart Timeframes (Renko/Range/Ticks).";
                    Chart.DrawStaticText("txt", $"{Msg}", V_Align, H_Align, Color.Orange);
                    Wrong = true;
                    return;
                }
                if (!timeBased.Any(CustomInterval.Name.ToString().Contains))
                {
                    string Msg = $"Weekly and Monthly 'Interval' should have 'Bars Source' above Minutes";
                    if (BarsVOLSource_TF.Name.Contains("Minute")) {
                        Chart.DrawStaticText("txt", $"{Msg}", V_Align, H_Align, Color.Orange);
                        Wrong = true;
                        return;
                    }
                }
                if (CustomInterval == Chart.TimeFrame || CustomInterval < Chart.TimeFrame)
                {
                    string comp = CustomInterval == Chart.TimeFrame ? "==" : "<";
                    string Msg = $"Volume Interval ({CustomInterval.ShortName}) {comp} Chart Timeframe ({Chart.TimeFrame.ShortName})\nWhy?";
                    Chart.DrawStaticText("txt", $"{Msg}", V_Align, H_Align, Color.Orange);
                    Wrong = true;
                    return;
                }
                if (CustomInterval < TimeFrame.Minute15)
                {
                    string Msg = "The minimum 'Custom Interval' is 15 minutes";
                    Chart.DrawStaticText("txt", $"{Msg}", V_Align, H_Align, Color.Orange);
                    Wrong = true;
                    return;
                }
                LookBack_TF = CustomInterval;
                HeightPips = CustomHeight;
            }

            void SetHeightPips(double digits5, double digits2)
            {
                if (Symbol.Digits == 5)
                    HeightPips = digits5;
                else if (Symbol.Digits == 2)
                {
                    HeightPips = digits2;
                    if (Symbol.PipSize == 0.1)
                        HeightPips /= 2;
                }
            }
            string[] timesBased = { "Minute", "Hour", "Daily", "Day" };
            if (!timesBased.Any(BarsVOLSource_TF.Name.ToString().Contains))
            {
                string Msg = $"'Bars Volume Source' is designed ONLY for: \n (Minutes, Hours, Days) \n Weekly and Monthly is not currently supported";
                Chart.DrawStaticText("txt", $"{Msg}", V_Align, H_Align, Color.Orange);
                Wrong = true;
                return;
            }
            if ((VolumeModeInput == VolumeModeData.Buy_Sell || VolumeModeInput == VolumeModeData.Delta || VolumeModeInput == VolumeModeData.Normal_Delta) && BarsVOLSource_TF != TimeFrame.Minute)
            {
                string Msg = $"'Buy_Sell' and 'Delta' is designed ONLY for '1m Bars Volume Source'";
                Chart.DrawStaticText("txt", $"{Msg}", V_Align, H_Align, Color.Orange);
                Wrong = true;
                return;
            }
            // ============================================================================

            LookBack_Bars = MarketData.GetBars(LookBack_TF);
            if (LookBack_Bars.ClosePrices.Count < Lookback)
            {
                while (LookBack_Bars.ClosePrices.Count < Lookback)
                {
                    int loadedCount = LookBack_Bars.LoadMoreHistory();
                    Print($"Loaded {loadedCount}, {LookBack_TF.ShortName} LookBack Bars, Current Bar Date: {LookBack_Bars.OpenTimes.FirstOrDefault()}");
                    if (loadedCount == 0)
                        break;
                }
            }

            if (VolumeSourceInput == VolumeSourceData.Ticks)
                TicksOHLC = MarketData.GetBars(TimeFrame.Tick);
            else
                VOL_Bars = MarketData.GetBars(BarsVOLSource_TF);

            if (VolumeSourceInput == VolumeSourceData.Bars)
            {
                if (VOL_Bars.OpenTimes.FirstOrDefault() > LookBack_Bars.OpenTimes[LookBack_Bars.ClosePrices.Count - Lookback])
                {
                    while (VOL_Bars.OpenTimes.FirstOrDefault() > LookBack_Bars.OpenTimes[LookBack_Bars.ClosePrices.Count - Lookback])
                    {
                        int loadedCount = VOL_Bars.LoadMoreHistory();
                        Print($"Loaded {loadedCount}, {BarsVOLSource_TF.ShortName} VOL Bars, Current Bar Date: {VOL_Bars.OpenTimes.FirstOrDefault()}");
                        if (loadedCount == 0)
                            break;
                    }
                }
                try
                {
                    DateTime FirstVolDate = VOL_Bars.OpenTimes.FirstOrDefault();
                    ChartVerticalLine lineInfo = Chart.DrawVerticalLine("VolumeStart", FirstVolDate, Color.Red);
                    lineInfo.LineStyle = LineStyle.Lines;
                    ChartText textInfo = Chart.DrawText($"VolumeStartText", $"{BarsVOLSource_TF.ShortName} Volume Data \n ends here", FirstVolDate, Bars.HighPrices[Bars.OpenTimes.GetIndexByTime(FirstVolDate)], Color.Red);
                    textInfo.FontSize = 8;
                }
                catch { };
            }
            else
                TickVolumeInitialize();

            // Ex: 4 pips to VOL Distribuition(rowHeight)
            rowHeight = Symbol.PipSize * HeightPips;
            drawHeight = Symbol.PipSize * (HeightPips / 2);

            DrawOnScreen("Calculating...");
            string nonTimeBased = !timesBased.Any(Chart.TimeFrame.ToString().Contains) ? "Ticks/Renko/Range with 100% Histogram Width \n sometimes is recommended" : "";
            string ticksInfo = $"Ticks Volume Source: \n 1) Naturally heavier at 1 tick \n 2) Large 'Lookback' or 'Tick Data' takes longer to calculate \n 3) Recommended for intraday only";
            string showTicksInfo = VolumeSourceInput == VolumeSourceData.Ticks ? ticksInfo : "";
            Second_DrawOnScreen($"Taking too long? You can: \n 1) Increase the rowHeight \n 2) Disable the Value Area (High Performance)\n\n {nonTimeBased} \n\n {showTicksInfo}");
            if (Application.UserTimeOffset.ToString() != "03:00:00")
                Third_DrawOnScreen("Set your UTC to UTC+3");

            // PARAMS PANEL
            VerticalAlignment vAlign = VerticalAlignment.Bottom;
            HorizontalAlignment hAlign = HorizontalAlignment.Right;

            if (PanelAlignInput == PanelAlignData.Bottom_Left)
                hAlign = HorizontalAlignment.Left;
            else if (PanelAlignInput == PanelAlignData.Top_Left)
                vAlign = VerticalAlignment.Top;
            else if (PanelAlignInput == PanelAlignData.Top_Right) {
                vAlign = VerticalAlignment.Top;
                hAlign = HorizontalAlignment.Right;
            } else if (PanelAlignInput == PanelAlignData.Center_Right) {
                vAlign = VerticalAlignment.Center;
                hAlign = HorizontalAlignment.Right;
            } else if (PanelAlignInput == PanelAlignData.Center_Left) {
                vAlign = VerticalAlignment.Center;
                hAlign = HorizontalAlignment.Left;
            } else if (PanelAlignInput == PanelAlignData.Top_Center) {
                vAlign = VerticalAlignment.Top;
                hAlign = HorizontalAlignment.Center;
            } else if (PanelAlignInput == PanelAlignData.Bottom_Center) {
                vAlign = VerticalAlignment.Bottom;
                hAlign = HorizontalAlignment.Center;
            }

            IndicatorParams DefaultParams = new()
            {
                LookBack = Lookback,
                VolMode = VolumeModeInput,
                RowHeight = rowHeight,
                Interval = LookBack_TF,
                KeepPOC = KeepPOC,
                ExtendedPOC = ExtendPOC,
                ShowVA = ShowVA
            };

            ParamsPanel ParamPanel = new(this, DefaultParams);
            Border borderParam = new()
            {
                VerticalAlignment = vAlign,
                HorizontalAlignment = hAlign,
                Style = Styles.CreatePanelBackgroundStyle(),
                Margin = "20 40 20 20",
                Width = 225,
                Child = ParamPanel
            };
            Chart.AddControl(borderParam);
            ParamBorder = borderParam;

            var wrapPanel = new WrapPanel
            {
                VerticalAlignment = vAlign,
                HorizontalAlignment = hAlign,
            };
            AddHiddenButton(wrapPanel, Color.Gray);
            Chart.AddControl(wrapPanel);
        }

        public override void Calculate(int index)
        {
            if (Wrong)
                return;

            // ==== Removing Messages ====
            if (!IsLastBar)
            {
                DrawOnScreen(""); Second_DrawOnScreen(""); Third_DrawOnScreen("");
            }

            Bars TF_Bars = LookBack_Bars;
            // Get Index of VOL Interval to continue only in Lookback
            int iVerify = TF_Bars.OpenTimes.GetIndexByTime(Bars.OpenTimes[index]);
            if (TF_Bars.ClosePrices.Count - iVerify > Lookback)
                return;

            int TF_idx = TF_Bars.OpenTimes.GetIndexByTime(Bars.OpenTimes[index]);
            int indexStart = Bars.OpenTimes.GetIndexByTime(TF_Bars.OpenTimes[TF_idx]);

            // ====== Extended POC/VA ========
            if (ExtendPOC && POCsLines.Count != 0 && POCsLines.FirstOrDefault().Y2 != priceVA_LHP[2])
            {
                for (int tl = 0; tl < POCsLines.Count; tl++)
                {
                    POCsLines[tl].Time2 = Bars.OpenTimes[index];
                    string dynDate = LookBack_TF == TimeFrame.Daily ? POCsLines[tl].Time1.Date.AddDays(1).ToString().Replace("00:00:00", "") : POCsLines[tl].Time1.Date.ToString();
                    Chart.DrawText($"POC{POCsLines[tl].Time1}", $"{dynDate}", Bars.OpenTimes[index], POCsLines[tl].Y2 + drawHeight, ColorPOC);
                }
            }

            if (ExtendVA && VALines.Count != 0 && VALines.FirstOrDefault().Time2 != Bars.OpenTimes[index])
            {
                for (int tl = 0; tl < VALines.Count; tl++)
                    VALines[tl].Time2 = Bars.OpenTimes[index];
            }

            // === Clean Dicts/others ===
            if (index == indexStart && index != cleanedIndex || (index - 1) == indexStart && (index - 1) != cleanedIndex)
            {
                Segments.Clear();
                VolumesRank.Clear();
                VolumesRank_Up.Clear();
                VolumesRank_Down.Clear();
                DeltaRank.Clear();
                RectanglesToColor.Clear();
                double[] VAforColor = { 0, 0, 0 };
                priceVA_LHP = VAforColor;
                cleanedIndex = index == indexStart ? index : (index - 1);
            }
            // Historical data
            if (!IsLastBar)
            {
                if (!isLive)
                    VolumeProfile(indexStart, index);
            }
            else
            {
                isLive = true;
                // "Repaint" if the price moves half of rowHeight
                if (Bars.ClosePrices[index] >= (prevPrice + drawHeight) || Bars.ClosePrices[index] <= (prevPrice - drawHeight) || configHasChanged)
                {
                    for (int i = indexStart; i <= index; i++)
                    {
                        if (i == indexStart)
                        {
                            Segments.Clear();
                            VolumesRank.Clear();
                            VolumesRank_Up.Clear();
                            VolumesRank_Down.Clear();
                            DeltaRank.Clear();
                            RectanglesToColor.Clear();
                        }

                        VolumeProfile(indexStart, i);
                    }
                    prevPrice = Bars.ClosePrices[index];
                    configHasChanged = false;
                }
            }
        }

        private void VolumeProfile(int iStart, int index)
        {
            // ======= Highest and Lowest =======
            Bars TF_Bars = LookBack_Bars;
            int TF_idx = TF_Bars.OpenTimes.GetIndexByTime(Bars.OpenTimes[index]);

            double highest = TF_Bars.HighPrices[TF_idx], lowest = TF_Bars.LowPrices[TF_idx], open = TF_Bars.OpenPrices[TF_idx];

            // ======= Chart Segmentation =======
            List<double> currentSegments = new();
            double prev_segment = open;
            while (prev_segment >= (lowest - rowHeight))
            {
                currentSegments.Add(prev_segment);
                prev_segment = Math.Abs(prev_segment - rowHeight);
            }
            prev_segment = open;
            while (prev_segment <= (highest + rowHeight))
            {
                currentSegments.Add(prev_segment);
                prev_segment = Math.Abs(prev_segment + rowHeight);
            }
            Segments = currentSegments.OrderBy(x => x).ToList();

            // ======= VP =======
            if (VolumeSourceInput == VolumeSourceData.Ticks)
                VP_Tick(index);
            else
                VP_Bars(index);

            // ======= Drawing =======
            if (Segments.Count == 0)
                return;

            for (int i = 0; i < Segments.Count; i++)
            {
                double priceKey = Segments[i];
                if (!VolumesRank.ContainsKey(priceKey))
                    continue;

                /*
                Indeed, the value of X-Axis is simply a rule of three,
                where the maximum value will be the maxLength (in Milliseconds),
                from there the math adjusts the histograms.

                    MaxValue    maxLength(ms)
                       x             ?(ms)

                The values 1.25 and 4 are the manually set values
                */

                double lowerSegment = Segments[i] - rowHeight;
                double upperSegment = Segments[i];

                double largestVOL = VolumesRank.Values.Max();

                double maxLength = Bars[index].OpenTime.Subtract(Bars[iStart].OpenTime).TotalMilliseconds;
                var selected = HistWidthInput;
                double maxWidth = selected == HistWidthData._15 ? 1.25 : selected == HistWidthData._30 ? 1.50 : selected == HistWidthData._50 ? 2 : 4;
                double proportion = VolumesRank[priceKey] * (maxLength - (maxLength / maxWidth));
                if (selected == HistWidthData._100)
                    proportion = VolumesRank[priceKey] * maxLength;

                double dynLength = proportion / largestVOL;

                Color dynColor = HistColor;
                if (VolumeModeInput == VolumeModeData.Gradient)
                {

                    double Intensity = (VolumesRank[priceKey] * 100 / largestVOL) / 100;
                    double stepR = (ColorGrandient_Max.R - ColorGrandient_Min.R) * Intensity;
                    double stepG = (ColorGrandient_Max.G - ColorGrandient_Min.G) * Intensity;
                    double stepB = (ColorGrandient_Max.B - ColorGrandient_Min.B) * Intensity;

                    int A = (int)(2.55 * OpacityHistInput);
                    int R = (int)Math.Round(ColorGrandient_Min.R + stepR);
                    int G = (int)Math.Round(ColorGrandient_Min.G + stepG);
                    int B = (int)Math.Round(ColorGrandient_Min.B + stepB);

                    dynColor = Color.FromArgb(A, R, G, B);
                }

                if (VolumeModeInput == VolumeModeData.Normal || VolumeModeInput == VolumeModeData.Normal_Delta || VolumeModeInput == VolumeModeData.Gradient)
                {
                    ChartRectangle volHist;
                    volHist = Chart.DrawRectangle($"{iStart}_{i}_", Bars.OpenTimes[iStart], lowerSegment, Bars.OpenTimes[iStart].AddMilliseconds(dynLength), upperSegment, dynColor);

                    if (RectanglesToColor.ContainsKey(i))
                        RectanglesToColor[i] = volHist;
                    else
                        RectanglesToColor.Add(i, volHist);

                    if (FillHist)
                        volHist.IsFilled = true;
                    if (HistogramSideInput == HistSideData.Right)
                    {
                        volHist.Time1 = Bars.OpenTimes[index];
                        volHist.Time2 = Bars.OpenTimes[index].AddMilliseconds(-dynLength);
                    }
                }
                if (VolumeModeInput == VolumeModeData.Buy_Sell)
                {
                    // Buy vs Sell = Pseudo Delta
                    double buy_Volume = 0;
                    try { buy_Volume = VolumesRank_Up.Values.Max(); } catch { };
                    double sell_Volume = 0;
                    try { sell_Volume = VolumesRank_Down.Values.Max(); } catch { };
                    double sideVolMax = buy_Volume > sell_Volume ? buy_Volume : sell_Volume;

                    double maxHalfWidth = selected == HistWidthData._15 ? 1.12 : selected == HistWidthData._30 ? 1.25 : selected == HistWidthData._50 ? 1.40 : 1.75;

                    double proportion_Up = 0;
                    try { proportion_Up = VolumesRank_Up[priceKey] * (maxLength - (maxLength / maxHalfWidth)); } catch { };
                    if (selected == HistWidthData._100)
                        try { proportion_Up = VolumesRank_Up[priceKey] * (maxLength - (maxLength / 3)); } catch { };

                    double dynLength_Up = proportion_Up / sideVolMax; ;

                    double proportion_Down = 0;
                    try { proportion_Down = VolumesRank_Down[priceKey] * (maxLength - (maxLength / maxWidth)); } catch { };
                    if (selected == HistWidthData._100)
                        try { proportion_Down = VolumesRank_Down[priceKey] * maxLength; } catch { };

                    double dynLength_Down = proportion_Down / sideVolMax;

                    ChartRectangle buyHist, sellHist;
                    if (VolumesRank_Down.ContainsKey(priceKey) && VolumesRank_Up.ContainsKey(priceKey))
                    {
                        sellHist = Chart.DrawRectangle($"{iStart}_{i}Sell", Bars.OpenTimes[iStart], lowerSegment, Bars[iStart].OpenTime.AddMilliseconds(dynLength_Down), upperSegment, SellColor);
                        buyHist = Chart.DrawRectangle($"{iStart}_{i}Buy", Bars.OpenTimes[iStart], lowerSegment, Bars[iStart].OpenTime.AddMilliseconds(dynLength_Up), upperSegment, BuyColor);
                        if (FillHist)
                        {
                            buyHist.IsFilled = true; sellHist.IsFilled = true;
                        }
                        if (HistogramSideInput == HistSideData.Right)
                        {
                            sellHist.Time1 = Bars.OpenTimes[index];
                            sellHist.Time2 = Bars.OpenTimes[index].AddMilliseconds(-dynLength_Down);
                            buyHist.Time1 = Bars.OpenTimes[index];
                            buyHist.Time2 = Bars.OpenTimes[index].AddMilliseconds(-dynLength_Up);
                        }
                    }
                    if (ShowResults)
                    {
                        double volBuy = VolumesRank_Up.Values.Sum();
                        double volSell = VolumesRank_Down.Values.Sum();
                        double percentBuy = (volBuy * 100) / (volBuy + volSell);
                        double percentSell = (volSell * 100) / (volBuy + volSell);

                        ChartText Left, Right;
                        Left = Chart.DrawText($"{iStart}BuySum", $"{Math.Round(percentBuy)}%", Bars.OpenTimes[iStart], lowest, SellColor);
                        Right = Chart.DrawText($"{iStart}SellSum", $"{Math.Round(percentSell)}%", Bars.OpenTimes[iStart], lowest, BuyColor);
                        Left.HorizontalAlignment = HorizontalAlignment.Left; Left.FontSize = FontSizeResults;
                        Right.HorizontalAlignment = HorizontalAlignment.Right; Right.FontSize = FontSizeResults;
                        if (HistogramSideInput == HistSideData.Right)
                        {
                            Right.Time = Bars.OpenTimes[index];
                            Left.Time = Bars.OpenTimes[index];
                        }
                    }
                }
                else if (VolumeModeInput == VolumeModeData.Delta || VolumeModeInput == VolumeModeData.Normal_Delta)
                {
                    // Delta
                    double Positive_Delta = DeltaRank.Values.Max();
                    IEnumerable<double> allNegative = DeltaRank.Values.Where(n => n < 0);
                    double Negative_Delta = 0;
                    try { Negative_Delta = Math.Abs(allNegative.Min()); } catch { }

                    double deltaMax = Positive_Delta > Negative_Delta ? Positive_Delta : Negative_Delta;

                    double proportion_Delta = Math.Abs(DeltaRank[priceKey]) * (maxLength - (maxLength / maxWidth));
                    if (selected == HistWidthData._100)
                        proportion_Delta = Math.Abs(DeltaRank[priceKey]) * maxLength;
                    double dynLength_Delta = proportion_Delta / deltaMax;

                    ChartRectangle deltaHist;
                    try
                    {
                        if (DeltaRank[priceKey] >= 0)
                            deltaHist = Chart.DrawRectangle($"{iStart}_{i}ProfileDelta", Bars.OpenTimes[iStart], lowerSegment, Bars[iStart].OpenTime.AddMilliseconds(dynLength_Delta), upperSegment, BuyColor);
                        else
                            deltaHist = Chart.DrawRectangle($"{iStart}_{i}ProfileDelta", Bars.OpenTimes[iStart], lowerSegment, Bars[iStart].OpenTime.AddMilliseconds(dynLength_Delta), upperSegment, SellColor);
                    }
                    catch
                    {
                        deltaHist = Chart.DrawRectangle($"{iStart}_{i}ProfileDelta", Bars.OpenTimes[iStart], lowerSegment, Bars[iStart].OpenTime, upperSegment, HistColor);
                    }
                    if (FillHist)
                        deltaHist.IsFilled = true;
                    if (HistogramSideInput == HistSideData.Right)
                    {
                        deltaHist.Time1 = Bars.OpenTimes[index];
                        deltaHist.Time2 = deltaHist.Time2 != Bars[iStart].OpenTime ? Bars.OpenTimes[index].AddMilliseconds(-dynLength_Delta) : Bars[iStart].OpenTime;
                    }

                    if (ShowResults)
                    {
                        double deltaBuy = DeltaRank.Values.Where(n => n > 0).Sum();
                        double deltaSell = DeltaRank.Values.Where(n => n < 0).Sum();
                        double percentBuy = 0;
                        double percentSell = 0;
                        try { percentBuy = (deltaBuy * 100) / (deltaBuy + Math.Abs(deltaSell)); } catch { };
                        try { percentSell = (deltaSell * 100) / (deltaBuy + Math.Abs(deltaSell)); } catch { }

                        ChartText Left, Right;
                        Right = Chart.DrawText($"{iStart}BuyDeltaSum", $"{Math.Round(percentBuy)}%", Bars.OpenTimes[iStart], lowest, BuyColor);
                        Left = Chart.DrawText($"{iStart}SellDeltaSum", $"{Math.Round(percentSell)}%", Bars.OpenTimes[iStart], lowest, SellColor);
                        Left.HorizontalAlignment = HorizontalAlignment.Left; Left.FontSize = FontSizeResults;
                        Right.HorizontalAlignment = HorizontalAlignment.Right; Right.FontSize = FontSizeResults;

                        if (HistogramSideInput == HistSideData.Right)
                        {
                            Right.Time = Bars.OpenTimes[index];
                            Left.Time = Bars.OpenTimes[index];
                        }
                    }
                }
                // ============= Coloring Letters + VAL / VAH / POC =============
                if (ShowVA)
                {
                    double[] VAL_VAH_POC = VA_Calculation();

                    // ==========================
                    ChartTrendLine poc = Chart.DrawTrendLine($"POC_{iStart}", TF_Bars.OpenTimes[TF_idx], VAL_VAH_POC[2] - rowHeight, Bars.OpenTimes[index], VAL_VAH_POC[2] - rowHeight, ColorPOC);
                    ChartTrendLine vah = Chart.DrawTrendLine($"VAH_{iStart}", TF_Bars.OpenTimes[TF_idx], VAL_VAH_POC[1] + rowHeight, Bars.OpenTimes[index], VAL_VAH_POC[1] + rowHeight, ColorVAH);
                    ChartTrendLine val = Chart.DrawTrendLine($"VAL_{iStart}", TF_Bars.OpenTimes[TF_idx], VAL_VAH_POC[0], Bars.OpenTimes[index], VAL_VAH_POC[0], ColorVAL);

                    double[] VAforColor = { VAL_VAH_POC[0], VAL_VAH_POC[1], VAL_VAH_POC[2] };
                    priceVA_LHP = VAforColor;

                    poc.LineStyle = LineStylePOC; poc.Thickness = ThicknessPOC; poc.Comment = "POC";
                    vah.LineStyle = LineStyleVA; vah.Thickness = ThicknessVA; vah.Comment = "VAH";
                    val.LineStyle = LineStyleVA; val.Thickness = ThicknessVA; val.Comment = "VAL";

                    // ==== POC Lines ====
                    if (POCsLines.Contains(poc))
                    {
                        for (int tl = 0; tl < RectanglesToColor.Count; tl++)
                        {
                            if (POCsLines[tl].Time1 == poc.Time1)
                            {
                                POCsLines[tl] = poc;
                                break;
                            }
                        }
                    }
                    else
                        POCsLines.Add(poc);

                    // ==== VAH / VAL Lines ====
                    if (VALines.Contains(vah) || VALines.Contains(val))
                    {
                        for (int tl = 0; tl < VALines.Count; tl++)
                        {
                            if (VALines[tl].Comment == "VAH" && VALines[tl].Time1 == vah.Time1)
                                VALines[tl] = vah;
                            else if (VALines[tl].Comment == "VAL" && VALines[tl].Time1 == val.Time1)
                                VALines[tl] = val;
                        }
                    }
                    else
                    {
                        if (!VALines.Contains(vah))
                            VALines.Add(vah);
                        if (!VALines.Contains(val))
                            VALines.Add(val);
                    }

                    if (VolumeModeInput == VolumeModeData.Normal)
                    {
                        // =========== Coloring Retangles ============
                        foreach (int key in RectanglesToColor.Keys)
                        {
                            if (RectanglesToColor[key].Y1 > priceVA_LHP[0] && RectanglesToColor[key].Y1 < priceVA_LHP[1])
                                RectanglesToColor[key].Color = HistColorVA;

                            if (RectanglesToColor[key].Y1 == priceVA_LHP[2] - rowHeight)
                                RectanglesToColor[key].Color = ColorPOC;

                            if (RectanglesToColor[key].Y1 == priceVA_LHP[1])
                                RectanglesToColor[key].Color = ColorVAH;
                            else if (RectanglesToColor[key].Y1 == priceVA_LHP[0])
                                RectanglesToColor[key].Color = ColorVAL;
                        }
                    }
                    else
                    {
                        foreach (int key in RectanglesToColor.Keys)
                        {
                            if (RectanglesToColor[key].Y1 == priceVA_LHP[2] - rowHeight)
                                RectanglesToColor[key].Color = ColorPOC;
                        }
                    }
                }
                else if (!ShowVA && KeepPOC)
                {
                    double priceLVOL = 0;
                    for (int k = 0; k < VolumesRank.Count; k++)
                    {
                        if (VolumesRank.ElementAt(k).Value == largestVOL)
                        {
                            priceLVOL = VolumesRank.ElementAt(k).Key;
                            break;
                        }
                    }

                    ChartTrendLine poc = Chart.DrawTrendLine($"POC_{iStart}", TF_Bars.OpenTimes[TF_idx], priceLVOL - rowHeight, Bars.OpenTimes[index], priceLVOL - rowHeight, ColorPOC);
                    poc.LineStyle = LineStylePOC; poc.Thickness = ThicknessPOC; poc.Comment = "POC";

                    // ==== POC Lines ====
                    if (POCsLines.Contains(poc))
                    {
                        for (int tl = 0; tl < RectanglesToColor.Count; tl++)
                        {
                            if (POCsLines[tl].Time1 == poc.Time1)
                            {
                                POCsLines[tl] = poc;
                                break;
                            }
                        }
                    }
                    else
                        POCsLines.Add(poc);

                    // =========== Coloring Retangles ============
                    foreach (int key in RectanglesToColor.Keys)
                    {
                        if (RectanglesToColor[key].Y1 == priceLVOL - rowHeight)
                            RectanglesToColor[key].Color = ColorPOC;
                    }
                }
            }
            // ====== Rectangle VA ======
            if (ShowVA && priceVA_LHP[0] != 0)
            {
                ChartRectangle rectVA;
                rectVA = Chart.DrawRectangle($"{TF_Bars.OpenTimes[TF_idx]}", TF_Bars.OpenTimes[TF_idx], priceVA_LHP[0], Bars.OpenTimes[index], priceVA_LHP[1] + rowHeight, VAColor);
                rectVA.IsFilled = true;
            }

            if (!ShowOHLC)
                return;
            ChartText iconOpenSession = Chart.DrawText($"Start{TF_Bars.OpenTimes[TF_idx]}", "▂", TF_Bars.OpenTimes[TF_idx], TF_Bars.OpenPrices[TF_idx], ColorOHLC);
            ChartText iconCloseSession = Chart.DrawText($"End{TF_Bars.OpenTimes[TF_idx]}", "▂", TF_Bars.OpenTimes[TF_idx], Bars.ClosePrices[index], ColorOHLC);
            iconOpenSession.VerticalAlignment = VerticalAlignment.Center;
            iconOpenSession.HorizontalAlignment = HorizontalAlignment.Left;
            iconOpenSession.FontSize = 14;
            iconCloseSession.VerticalAlignment = VerticalAlignment.Center;
            iconCloseSession.HorizontalAlignment = HorizontalAlignment.Right;
            iconCloseSession.FontSize = 14;

            ChartTrendLine Session = Chart.DrawTrendLine($"Session{TF_Bars.OpenTimes[TF_idx]}", TF_Bars.OpenTimes[TF_idx], lowest, TF_Bars.OpenTimes[TF_idx], highest, ColorOHLC);
            Session.Thickness = 3;
        }
        private void VP_Bars(int index)
        {

            DateTime startTime = Bars.OpenTimes[index];
            DateTime endTime = Bars.OpenTimes[index + 1];

            if (IsLastBar)
                endTime = VOL_Bars.Last().OpenTime;

            for (int k = 0; k < VOL_Bars.Count; ++k)
            {
                Bar volBar;
                volBar = VOL_Bars[k];

                if (volBar.OpenTime < startTime || volBar.OpenTime > endTime)
                {
                    if (volBar.OpenTime > endTime)
                        break;
                    else
                        continue;
                }
                /* The Volume Calculation(in Bars Volume Source) is exported, with adaptations, from the BEST VP I have see/used for MT4/MT5,
                    of Russian FXcoder's https://gitlab.com/fxcoder-mql/vp (VP 10.1), author of the famous (Volume Profile + Range v6.0)
                / I tried to reproduce as close to the original,
                / I would say it was very good approximation in most core options,
                / except the "Triangular", witch I had to interpret it my way, and it turned out different, of course.
                / "Parabolic" too but the result turned out good
                */
                bool isBullish = volBar.Close >= volBar.Open;
                if (DistributionInput == DistributionData.OHLC)
                {
                    // ========= Tick Simulation ================
                    // Bull/Buy/Up bar
                    if (volBar.Close >= volBar.Open)
                    {
                        // Average Tick Volume
                        double avgVol = volBar.TickVolume / (volBar.Open + volBar.High + volBar.Low + volBar.Close / 4);
                        for (int i = 0; i < Segments.Count; i++)
                        {
                            double priceKey = Segments[i];
                            if (Segments[i] <= volBar.Open && Segments[i] >= volBar.Low)
                                AddVolume(priceKey, avgVol, isBullish);
                            if (Segments[i] <= volBar.High && Segments[i] >= volBar.Low)
                                AddVolume(priceKey, avgVol, isBullish);
                            if (Segments[i] <= volBar.High && Segments[i] >= volBar.Close)
                                AddVolume(priceKey, avgVol, isBullish);
                        }
                    }
                    // Bear/Sell/Down bar
                    else
                    {
                        // Average Tick Volume
                        double avgVol = volBar.TickVolume / (volBar.Open + volBar.High + volBar.Low + volBar.Close / 4);
                        for (int i = 0; i < Segments.Count; i++)
                        {
                            double priceKey = Segments[i];
                            if (Segments[i] >= volBar.Open && Segments[i] <= volBar.High)
                                AddVolume(priceKey, avgVol, isBullish);
                            if (Segments[i] <= volBar.High && Segments[i] >= volBar.Low)
                                AddVolume(priceKey, avgVol, isBullish);
                            if (Segments[i] >= volBar.Low && Segments[i] <= volBar.Close)
                                AddVolume(priceKey, avgVol, isBullish);
                        }
                    }
                }
                else if (DistributionInput == DistributionData.High || DistributionInput == DistributionData.Low || DistributionInput == DistributionData.Close)
                {
                    var selected = DistributionInput;
                    if (selected == DistributionData.High)
                    {
                        double prevSegment = 0;
                        for (int i = 0; i < Segments.Count; i++)
                        {
                            if (Segments[i] >= volBar.High && prevSegment <= volBar.High)
                                AddVolume(Segments[i], volBar.TickVolume, isBullish);
                            prevSegment = Segments[i];
                        }
                    }
                    else if (selected == DistributionData.Low)
                    {
                        double prevSegment = 0;
                        for (int i = 0; i < Segments.Count; i++)
                        {
                            if (Segments[i] >= volBar.Low && prevSegment <= volBar.Low)
                                AddVolume(Segments[i], volBar.TickVolume, isBullish);
                            prevSegment = Segments[i];
                        }
                    }
                    else
                    {
                        double prevSegment = 0;
                        for (int i = 0; i < Segments.Count; i++)
                        {
                            if (Segments[i] >= volBar.Close && prevSegment <= volBar.Close)
                                AddVolume(Segments[i], volBar.TickVolume, isBullish);
                            prevSegment = Segments[i];
                        }
                    }
                }
                else if (DistributionInput == DistributionData.Uniform_Distribution)
                {
                    double HL = Math.Abs(volBar.High - volBar.Low);
                    double uniVol = volBar.TickVolume / HL;
                    for (int i = 0; i < Segments.Count; i++)
                    {
                        if (Segments[i] >= volBar.Low && Segments[i] <= volBar.High)
                            AddVolume(Segments[i], uniVol, isBullish);
                    }
                }
                else if (DistributionInput == DistributionData.Uniform_Presence)
                {
                    double uniP_Vol = 1;
                    for (int i = 0; i < Segments.Count; i++)
                    {
                        if (Segments[i] >= volBar.Low && Segments[i] <= volBar.High)
                            AddVolume(Segments[i], uniP_Vol, isBullish);
                    }
                }
                else if (DistributionInput == DistributionData.Parabolic_Distribution)
                {
                    double HL = Math.Abs(volBar.High - volBar.Low);
                    double HL2 = HL / 2;
                    double hl2SQRT = Math.Sqrt(HL2);
                    double final = hl2SQRT / hl2SQRT;

                    double parabolicVol = volBar.TickVolume / final;

                    for (int i = 0; i < Segments.Count; i++)
                    {
                        if (Segments[i] >= volBar.Low && Segments[i] <= volBar.High)
                            AddVolume(Segments[i], parabolicVol, isBullish);
                    }
                }
                else if (DistributionInput == DistributionData.Triangular_Distribution)
                {
                    double HL = Math.Abs(volBar.High - volBar.Low);
                    double HL2 = HL / 2;
                    double HL_minus = HL - HL2;
                    // =====================================
                    double oneStep = HL2 * HL2 / 2;
                    double secondStep = HL_minus * HL_minus / 2;
                    double final = oneStep + secondStep;

                    double triangularVol = volBar.TickVolume / final;

                    for (int i = 0; i < Segments.Count; i++)
                    {
                        if (Segments[i] >= volBar.Low && Segments[i] <= volBar.High)
                            AddVolume(Segments[i], triangularVol, isBullish);
                    }
                }
            }

            void AddVolume(double priceKey, double vol, bool isBullish)
            {
                if (!VolumesRank.ContainsKey(priceKey))
                    VolumesRank.Add(priceKey, vol);
                else
                    VolumesRank[priceKey] += vol;
                bool condition = VolumeModeInput != VolumeModeData.Normal || VolumeModeInput != VolumeModeData.Gradient;
                if (condition)
                    Add_BuySell(priceKey, vol, isBullish);
            }
            void Add_BuySell(double priceKey, double vol, bool isBullish)
            {
                if (isBullish)
                {
                    if (!VolumesRank_Up.ContainsKey(priceKey))
                        VolumesRank_Up.Add(priceKey, vol);
                    else
                        VolumesRank_Up[priceKey] += vol;
                }
                else
                {
                    if (!VolumesRank_Down.ContainsKey(priceKey))
                        VolumesRank_Down.Add(priceKey, vol);
                    else
                        VolumesRank_Down[priceKey] += vol;
                }

                if (!DeltaRank.ContainsKey(priceKey))
                {
                    if (VolumesRank_Up.ContainsKey(priceKey) && VolumesRank_Down.ContainsKey(priceKey))
                        DeltaRank.Add(priceKey, (VolumesRank_Up[priceKey] - VolumesRank_Down[priceKey]));
                    else if (VolumesRank_Up.ContainsKey(priceKey) && !VolumesRank_Down.ContainsKey(priceKey))
                        DeltaRank.Add(priceKey, (VolumesRank_Up[priceKey]));
                    else if (!VolumesRank_Up.ContainsKey(priceKey) && VolumesRank_Down.ContainsKey(priceKey))
                        DeltaRank.Add(priceKey, (-VolumesRank_Down[priceKey]));
                    else
                        DeltaRank.Add(priceKey, 0);
                }
                else
                {
                    if (VolumesRank_Up.ContainsKey(priceKey) && VolumesRank_Down.ContainsKey(priceKey))
                        DeltaRank[priceKey] += (VolumesRank_Up[priceKey] - VolumesRank_Down[priceKey]);
                    else if (VolumesRank_Up.ContainsKey(priceKey) && !VolumesRank_Down.ContainsKey(priceKey))
                        DeltaRank[priceKey] += (VolumesRank_Up[priceKey]);
                    else if (!VolumesRank_Up.ContainsKey(priceKey) && VolumesRank_Down.ContainsKey(priceKey))
                        DeltaRank[priceKey] += (-VolumesRank_Down[priceKey]);

                }
            }
        }

        // ====== Functions Area ======
        private void VP_Tick(int index)
        {
            DateTime startTime = Bars.OpenTimes[index];
            DateTime endTime = Bars.OpenTimes[index + 1];

            if (IsLastBar)
                endTime = TicksOHLC.Last().OpenTime;

            double prevTick = 0;

            for (int tickIndex = 0; tickIndex < TicksOHLC.Count; tickIndex++)
            {
                Bar tickBar;
                tickBar = TicksOHLC[tickIndex];

                if (tickBar.OpenTime < startTime || tickBar.OpenTime > endTime)
                {
                    if (tickBar.OpenTime > endTime)
                        break;
                    else
                        continue;
                }

                RankVol(tickBar.Close);
                prevTick = tickBar.Close;
            }
            // ========= ========== ==========
            void RankVol(double tickPrice)
            {
                double prev_segmentValue = 0.0;
                for (int i = 0; i < Segments.Count; i++)
                {
                    if (tickPrice >= prev_segmentValue && tickPrice <= Segments[i])
                    {
                        double priceKey = Segments[i];

                        if (VolumesRank.ContainsKey(priceKey))
                        {
                            VolumesRank[priceKey] += 1;

                            if (tickPrice > prevTick && prevTick != 0)
                                VolumesRank_Up[priceKey] += 1;
                            else if (tickPrice < prevTick && prevTick != 0)
                                VolumesRank_Down[priceKey] += 1;
                            else if (tickPrice == prevTick && prevTick != 0)
                            {
                                VolumesRank_Up[priceKey] += 1;
                                VolumesRank_Down[priceKey] += 1;
                            }

                            DeltaRank[priceKey] += (VolumesRank_Up[priceKey] - VolumesRank_Down[priceKey]);
                        }
                        else
                        {
                            VolumesRank.Add(priceKey, 1);

                            if (!VolumesRank_Up.ContainsKey(priceKey))
                                VolumesRank_Up.Add(priceKey, 1);
                            else
                                VolumesRank_Up[priceKey] += 1;

                            if (!VolumesRank_Down.ContainsKey(priceKey))
                                VolumesRank_Down.Add(priceKey, 1);
                            else
                                VolumesRank_Down[priceKey] += 1;

                            if (!DeltaRank.ContainsKey(priceKey))
                                DeltaRank.Add(priceKey, (VolumesRank_Up[priceKey] - VolumesRank_Down[priceKey]));
                            else
                                DeltaRank[priceKey] += (VolumesRank_Up[priceKey] - VolumesRank_Down[priceKey]);
                        }

                        break;
                    }
                    prev_segmentValue = Segments[i];
                }
            }
        }
        private void DrawOnScreen(string Msg)
        {
            Chart.DrawStaticText("txt", $"{Msg}", V_Align, H_Align, Color.LightBlue);
        }
        private void Second_DrawOnScreen(string Msg)
        {
            Chart.DrawStaticText("txt2", $"{Msg}", VerticalAlignment.Top, HorizontalAlignment.Left, Color.LightBlue);
        }
        private void Third_DrawOnScreen(string Msg)
        {
            Chart.DrawStaticText("txt3", $"{Msg}", VerticalAlignment.Top, HorizontalAlignment.Right, Color.Yellow);
        }
        private void TickVolumeInitialize()
        {
            if (LoadFromInput == LoadFromData.Custom)
            {
                // ==== Get datetime to load from: dd/mm/yyyy ====
                if (DateTime.TryParseExact(StringDate, "dd/mm/yyyy", new CultureInfo("en-US"), DateTimeStyles.None, out FromDateTime))
                {
                    if (FromDateTime > Server.Time.Date)
                    {
                        // for Log
                        FromDateTime = Server.Time.Date;
                        Print($"Invalid DateTime '{StringDate}'. Using '{FromDateTime}'");
                    }
                }
                else
                {
                    // for Log
                    FromDateTime = Server.Time.Date;
                    Print($"Invalid DateTime '{StringDate}'. Using '{FromDateTime}'");
                }
            }
            else
            {
                if (LoadFromInput != LoadFromData.According_to_Lookback)
                {
                    DateTime LastBarTime = Bars.LastBar.OpenTime.Date;
                    if (LoadFromInput == LoadFromData.Today)
                        FromDateTime = LastBarTime.Date;
                    else if (LoadFromInput == LoadFromData.Yesterday)
                        FromDateTime = LastBarTime.AddDays(-1);
                    else if (LoadFromInput == LoadFromData.One_Week)
                        FromDateTime = LastBarTime.AddDays(-5);

                    FromDateTime = FromDateTime.AddDays(-1).AddHours(21);
                }
                else
                    FromDateTime = LookBack_Bars.OpenTimes[LookBack_Bars.ClosePrices.Count - Lookback];

            }

            // ==== Check if existing ticks data on the chart really needs more data ====
            DateTime FirstTickTime = TicksOHLC.OpenTimes.FirstOrDefault();
            if (FirstTickTime >= FromDateTime)
            {
                LoadMoreTicks(FromDateTime);
                DrawOnScreen("Data Collection Finished \n Calculating...");
            }
            else
            {
                Print($"Using existing tick data from '{FirstTickTime}'");
                DrawOnScreen($"Using existing tick data from '{FirstTickTime}' \n Calculating...");
            }
            try
            {
                FirstTickTime = TicksOHLC.OpenTimes.FirstOrDefault();
                ChartVerticalLine lineInfo = Chart.DrawVerticalLine("VolumeStart", FirstTickTime, Color.Red);
                lineInfo.LineStyle = LineStyle.Lines;
                ChartText textInfo = Chart.DrawText($"VolumeStartText", $"Tick Volume Data \n ends here", FirstTickTime, Bars.HighPrices[Bars.OpenTimes.GetIndexByTime(FirstTickTime)], Color.Red);
                textInfo.FontSize = 8;
            }
            catch { };
        }
        private void LoadMoreTicks(DateTime FromDateTime)
        {
            bool msg = false;

            while (TicksOHLC.OpenTimes.FirstOrDefault() > FromDateTime)
            {
                if (!msg)
                {
                    Print($"Loading from '{TicksOHLC.OpenTimes.First()}' to '{FromDateTime}'...");
                    msg = true;
                }

                int loadedCount = TicksOHLC.LoadMoreHistory();
                Print("Loaded {0} Ticks, Current Tick Date: {1}", loadedCount, TicksOHLC.OpenTimes.FirstOrDefault());
                if (loadedCount == 0)
                    break;
            }
            Print("Data Collection Finished, First Tick from: {0}", TicksOHLC.OpenTimes.FirstOrDefault());
        }
        // ========= ========== ==========
        private double[] VA_Calculation()
        {
            /*  https://onlinelibrary.wiley.com/doi/pdf/10.1002/9781118659724.app1
                https://www.mypivots.com/dictionary/definition/40/calculating-market-profile-value-area
                Same of TPO Profile(https://ctrader.com/algos/indicators/show/3074)  */

            double largestVOL = VolumesRank.Values.Max();

            double totalvol = VolumesRank.Values.Sum();
            double _70percent = Math.Round((70 * totalvol) / 100);

            double priceLVOL = 0;
            for (int k = 0; k < VolumesRank.Count; k++)
            {
                if (VolumesRank.ElementAt(k).Value == largestVOL)
                {
                    priceLVOL = VolumesRank.ElementAt(k).Key;
                    break;
                }
            }
            double priceVAH = 0;
            double priceVAL = 0;

            double sumVA = largestVOL;

            List<double> upKeys = new();
            List<double> downKeys = new();
            for (int i = 0; i < Segments.Count; i++)
            {
                double priceKey = Segments[i];

                if (VolumesRank.ContainsKey(priceKey))
                {
                    if (priceKey < priceLVOL)
                        downKeys.Add(priceKey);
                    else if (priceKey > priceLVOL)
                        upKeys.Add(priceKey);
                }
            }
            upKeys.Sort();
            downKeys.Sort();
            downKeys.Reverse();

            double[] withoutVA = { priceLVOL - (rowHeight * 2), priceLVOL + drawHeight, priceLVOL };
            if (upKeys.Count == 0 || downKeys.Count == 0)
                return withoutVA;

            double[] prev2UP = { 0, 0 };
            double[] prev2Down = { 0, 0 };

            bool lockAbove = false;
            double[] aboveKV = { 0, 0 };

            bool lockBelow = false;
            double[] belowKV = { 0, 0 };

            for (int i = 0; i < VolumesRank.Keys.Count; i++)
            {
                if (sumVA >= _70percent)
                    break;

                doubl

srlcarlg's avatar
srlcarlg

Joined on 25.07.2022

  • Distribution: Free
  • Language: C#
  • Trading platform: cTrader Automate
  • File name: Free Volume Profile v2.0.algo
  • Rating: 5
  • Installs: 40
  • Modified: 13/12/2024 01:04
Comments
Log in to add a comment.
RS
rsujedy · 1 month ago

Buongiorno, per visualizzare l’indicatore con i range mi sai dire che impostazioni mettere.

grazie

AS
asad_rehman47 · 1 month ago

Hi is possible to have some user guide ?

TommyDee's avatar
TommyDee · 2 months ago

Is it possible that you can update this script so it will work with cTrader Ver. 5+

MI
mike.r.alderson · 3 months ago

Absolutely Fantastic, what a God-Send this Script is.

 

Thank you SO MUCH for providing this.

 

Happy Trading x

OG
Ogro · 8 months ago

Olá, meu caro amigo gostaria de parabeniza-lo pois seu indicador e uma obra prima muito obrigado por compartilha-lo de forma gratuita…gostaria de saber como contata-lo, pois estou precisando desenvolver um indicador que personalize minha área de trabalho no CTrader, se tiver interesse peço que entre em contato para conversarmos. abraço 

FE
fernando.web1001trader · 8 months ago

estou testando assim que concluit os testes farei um comentarei, antes disso não, porem agradeço ao esforço de colocar um indicador a comunidade

JA
javiertz777 · 10 months ago

thank you for sharing this great tool, I hope this comes back to you in the best posible way. Blessings ! 

AndreaPereira's avatar
AndreaPereira · 11 months ago

ottimo lavoro, grazie per averlo reso gratuito.

MU
mukrybaran · 1 year ago

hey sir. excellent work. is there any video tutorial how trade with this indicator. 

ctrader.guru's avatar
ctrader.guru · 1 year ago

Excellent work, congratulations!

TI
Timoor · 1 year ago

Thank you, it works: but the color library needs to be updated (( Warning CS0618: 'Colors' is obsolete: ))

SI
simonv76@live.com · 1 year ago

My fault, it works now!

SI
simonv76@live.com · 1 year ago

Hello, thanks for your awesome work.

I tried every kind of settings combinations , but I can't get (in Normal color Mode) the VA colored differently. It seems settings “normal color inside VA” doesn't work. at least for me.

Any Ideas? I'm using cTrader multi broker

AL
alebianchi73 · 2 years ago

Hello, very nice Volume Profile.

I have two questions:

1) How can I save the settings when I change it? Every time I close the indicator when I open it it shows again default settings.

2) When I select a custom interval the volume bars still shows as if it is weekly, regardless if I choose H4, D1 or Monthly for example. Am I playing with the wrong parameter to get the volume bars with a different interval on the chart?

Thank you in advance

MZ
mztd006 · 2 years ago

since volume and order block is your thing ! :) can we have a decent supply demand indicator like Advanced Supply Demand in ctrader? thank you

HI
hiba7rain · 2 years ago

@srlcarlg

Yes, something like this on right side of the chart, see the below links 

https://images.app.goo.gl/YwA6eDZNAk4mW5qp7

https://images.app.goo.gl/gLSJMoEjA79vsXoT6

Thanks will be great if you can code it 

srlcarlg's avatar
srlcarlg · 2 years ago

@hiba7rain

Humm, actually this already has Buy&Sell/Delta aggregate for any period, now if you want to use Order Flow Ticks Data, just put "Ticks" as "Volume Source".
I didn't put the sum of the values because the percentage on each side is what matters.

But maybe I'm not understanding your question, please specify better or clarify your question with examples or links.
If it's promising enough maybe I'll consider putting it on.

HI
hiba7rain · 2 years ago

@srlcarlg

thank you for your response, yes I can see that on Order flow ticks 

but if it is on the volume profile it will be great because we will be able to see the aggregate for the period wanted say for example for the day  

srlcarlg's avatar
srlcarlg · 2 years ago

@hiba7rain

Order Flow Ticks already do it

HI
hiba7rain · 2 years ago

is it possible to see the delta values at each bar? i.e. the value to be plotted next to each bar in real time 

WI
wigev58908 · 2 years ago

This product is a compact, one-piece molded system that features a fixed gradient color to create a stunning visual effect. It is made of a high-quality, lightweight, durable material. yellowstone john dutton green quilted jacket