Full Code of EarnForex/MarketProfile for AI

master f819c3c78379 cached
46 files
661.1 KB
155.6k tokens
233 symbols
1 requests
Download .txt
Showing preview only (687K chars total). Download the full file or copy to clipboard to get everything.
Repository: EarnForex/MarketProfile
Branch: master
Commit: f819c3c78379
Files: 46
Total size: 661.1 KB

Directory structure:
gitextract_kb5h78b7/

├── LICENSE
├── MarketProfile/
│   ├── MarketProfile/
│   │   ├── Calculator/
│   │   │   ├── MarketProfileCalculator.cs
│   │   │   └── MatrixPoint.cs
│   │   ├── Enums/
│   │   │   ├── AlertCheckBar.cs
│   │   │   ├── AlertTypes.cs
│   │   │   ├── Direction.cs
│   │   │   ├── PilingMode.cs
│   │   │   ├── SatSunSolution.cs
│   │   │   ├── SessionPeriod.cs
│   │   │   ├── SessionsToDrawRays.cs
│   │   │   ├── SinglePrintType.cs
│   │   │   ├── Transitions.cs
│   │   │   └── WaysToStopRays.cs
│   │   ├── EventArgs/
│   │   │   └── CalculateEventArgs.cs
│   │   ├── Helpers.cs
│   │   ├── Interfaces/
│   │   │   └── IIndicatorResources.cs
│   │   ├── ManagersAndFeatures/
│   │   │   ├── AlertManager.cs
│   │   │   ├── HideRaysFromInvisibleSessionsFeature.cs
│   │   │   ├── HotkeyStateManager.cs
│   │   │   ├── NewHighLowManager.cs
│   │   │   ├── OutputDevelopingValuesManager.cs
│   │   │   ├── SeamlessScrollingManager.cs
│   │   │   └── SessionChangeManager.cs
│   │   ├── MarketProfile.cs
│   │   ├── MarketProfile.csproj
│   │   ├── MarketProfileRenderer.cs
│   │   ├── Models/
│   │   │   ├── MarketProfileModel.cs
│   │   │   ├── MarketProfileSession.cs
│   │   │   └── SessionState.cs
│   │   ├── RangeCalculators/
│   │   │   ├── AnnualSessionProfileStrategy.cs
│   │   │   ├── DailySessionProfileStrategy.cs
│   │   │   ├── IRenderingModesResources.cs
│   │   │   ├── ISessionProfileStrategy.cs
│   │   │   ├── IntradaySessionProfileStrategy.cs
│   │   │   ├── MonthlySessionProfileStrategy.cs
│   │   │   ├── QuarterlySessionProfileStrategy.cs
│   │   │   ├── RectangleSessionProfileStrategy.cs
│   │   │   ├── SemiannualSessionProfileStrategy.cs
│   │   │   ├── SessionProfileStrategyFactory.cs
│   │   │   ├── SessionRange.cs
│   │   │   └── WeeklySessionProfileStrategy.cs
│   │   └── RectangleModeProcessor.cs
│   └── MarketProfile.sln
├── MarketProfile.mq4
├── MarketProfile.mq5
└── README.md

================================================
FILE CONTENTS
================================================

================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: MarketProfile/MarketProfile/Calculator/MarketProfileCalculator.cs
================================================
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using cAlgo.API;
using cAlgo.API.Internals;

namespace cAlgo;

public interface IMarketProfileResources
{
    double ValueAreaPercentage { get; }
    
     TimeFrame TimeFrame { get; }
     Bars Bars { get; }
     Symbol Symbol { get; }
     Chart Chart { get; }
     
     double OneTickSize { get; }
     
     void Print(object message);
}

/// <summary>
/// 
/// </summary>
/// <remarks>
/// Colors should not be tied to the calculations, but so far I found it convenient to do so
/// Inside MarketProfileRenderer, the color might be overriden with the direction of the bar
/// </remarks>
public class MarketProfileCalculator : IMarketProfileResources
{
    private readonly IMarketProfileResources _resources;

    public TimeSpan BarTimeSpan { get; set; }

    public MarketProfileCalculator(IMarketProfileResources resources)
    {
        _resources = resources;
        BarTimeSpan = Helpers.GetBarTimeSpan(TimeFrame);
    }

    /// <summary>
    /// Get a section of bars between startTime and endTime
    /// </summary>
    public IEnumerable<Bar> GetBarSection(DateTime startTime, DateTime endTime)
    {
        return Bars.Where(x => x.OpenTime >= startTime && x.OpenTime <= endTime);
    }
    
    /// <summary>
    /// The number of histograms is determined by the number of slices you want from the range you have
    /// </summary>
    /// <param name="bars"></param>
    /// <param name="numberOfSlices"></param>
    /// <returns></returns>
    public double GetHistogramSize(Bar[] bars, int numberOfSlices)
    {
        var max = bars.Select(x => x.High).Max();
        var min = bars.Select(x => x.Low).Min();
        
        var range = max - min;
        return range / numberOfSlices;
    }

    public int GetSlices(Bar[] bars)
    {
        var highMinusLow = bars.Max(x => x.High) - bars.Min(x => x.Low);
        return (int) (highMinusLow / OneTickSize);
    }
    
    /// <summary>
    /// This way of calculating the matrix is a bit different from the one above, because it fills and piles on each iteration
    /// instead of looping though the rows first, now it loops through the columns first, when it's done, it piles the matrix to the left
    /// this is useful to calculate the developing PoC and VAH/VAL
    /// </summary>
    /// <param name="bars"></param>
    /// <param name="numberOfSlices"></param>
    /// <param name="startColor"></param>
    /// <param name="endColor"></param>
    /// <returns></returns>
    public MarketProfileModel FillAndPileMatrixCalculation(Bar[] bars, int numberOfSlices, Color startColor, Color endColor)
    {
        var rows = numberOfSlices;
        var columns = bars.Length;
        var startTime = bars.First().OpenTime;
        var endTime = bars.Last().OpenTime;
        var histogramSize = GetHistogramSize(bars, numberOfSlices);
        var bottomOfRegion = bars.Select(x => x.Low).Min();
        var colors = Helpers.GenerateColorGradient(startColor, endColor, columns);
        var marketProfileModel = new MarketProfileModel();
        
        var matrix = new MatrixPoint[rows, columns];

        for (var column = 0; column < columns; column++)
        {
            var bar = bars[column];
            var pointsPerColumn = new Dictionary<int,MatrixPoint>();
            
            for (var row = 0; row < rows; row++)
            {
                var sliceBottom = bottomOfRegion + histogramSize * row;
                var sliceTop = sliceBottom + histogramSize;

                if (sliceTop < bar.Low || sliceBottom > bar.High)
                    continue;
                
                var point = new MatrixPoint
                {
                    Direction = bar.Close > bar.Open ? Direction.Up : Direction.Down,
                    StartTime = bar.OpenTime,
                    EndTime = column + 1 < columns ? bars[column + 1].OpenTime: bar.OpenTime.Add(BarTimeSpan),
                    Top = sliceBottom + histogramSize,
                    Bottom = sliceBottom,
                    Color = colors[column]
                };
                
                pointsPerColumn.Add(row, point);
            }
            
            //now we will pile all the points to the existing matrix
            foreach (var point in pointsPerColumn)
            {
                var row = point.Key;
                var pointToPile = point.Value;
                
                var newColumnIndex = GetTotalRowPoints(matrix, row);
                var newColumnBar = bars[newColumnIndex];
                
                var piledPoint = new MatrixPoint
                {
                    Direction = pointToPile.Direction,
                    StartTime = newColumnBar.OpenTime,
                    EndTime = newColumnIndex + 1 < columns ? bars[newColumnIndex + 1].OpenTime : newColumnBar.OpenTime.Add(BarTimeSpan),
                    Top = pointToPile.Top,
                    Bottom = pointToPile.Bottom,
                    Color = pointToPile.Color
                };
                
                matrix[row, newColumnIndex] = piledPoint;
            }
            
            var pocRowIndex = GetPointOfControlRowIndex(matrix);
            
            if (matrix[pocRowIndex, 0] == null)
            {
                marketProfileModel.DevelopingPoC.Add(bar.OpenTime, double.NaN);
                marketProfileModel.DevelopingAreaHigh.Add(bar.OpenTime, double.NaN);
                marketProfileModel.DevelopingAreaLow.Add(bar.OpenTime, double.NaN);
                continue;
            }

            var poc = GetPointOfControlPrice(matrix, pocRowIndex);
            var (valueAreaHigh, valueAreaLow) = GetValueArea(matrix, ValueAreaPercentage);
                
            marketProfileModel.DevelopingPoC.Add(bar.OpenTime, poc);
            if (!double.IsNaN(valueAreaHigh))
                marketProfileModel.DevelopingAreaHigh.Add(bar.OpenTime, valueAreaHigh);
            
            if (!double.IsNaN(valueAreaLow))
                marketProfileModel.DevelopingAreaLow.Add(bar.OpenTime, valueAreaLow);
        }
        
        marketProfileModel.Matrix = matrix;
        marketProfileModel.StartTime = startTime;
        marketProfileModel.EndTime = endTime;

        return marketProfileModel;
    }
    
    public MarketProfileModel FillAndPileMatrixCalculationCropped(
    Bar[] bars,
    int numberOfSlices,
    Color startColor,
    Color endColor,
    double cropTop,
    double cropBottom)
    {
        var rows = numberOfSlices;
        var columns = bars.Length;
        var startTime = bars.First().OpenTime;
        var endTime = bars.Last().OpenTime;
        var histogramSize = GetHistogramSize(bars, numberOfSlices);
        var bottomOfRegion = bars.Select(x => x.Low).Min();
        var colors = Helpers.GenerateColorGradient(startColor, endColor, columns);
        var marketProfileModel = new MarketProfileModel();

        var matrix = new MatrixPoint[rows, columns];

        for (var column = 0; column < columns; column++)
        {
            var bar = bars[column];
            var pointsPerColumn = new Dictionary<int, MatrixPoint>();

            for (var row = 0; row < rows; row++)
            {
                var sliceBottom = bottomOfRegion + histogramSize * row;
                var sliceTop = sliceBottom + histogramSize;

                // Only include slices within the crop range
                if (sliceTop < bar.Low || sliceBottom > bar.High)
                    continue;
                if (sliceTop > cropTop || sliceBottom < cropBottom)
                    continue;

                var point = new MatrixPoint
                {
                    Direction = bar.Close > bar.Open ? Direction.Up : Direction.Down,
                    StartTime = bar.OpenTime,
                    EndTime = column + 1 < columns ? bars[column + 1].OpenTime : bar.OpenTime.Add(BarTimeSpan),
                    Top = sliceBottom + histogramSize,
                    Bottom = sliceBottom,
                    Color = colors[column]
                };

                pointsPerColumn.Add(row, point);
            }

            foreach (var point in pointsPerColumn)
            {
                var row = point.Key;
                var pointToPile = point.Value;

                var newColumnIndex = GetTotalRowPoints(matrix, row);
                var newColumnBar = bars[newColumnIndex];

                var piledPoint = new MatrixPoint
                {
                    Direction = pointToPile.Direction,
                    StartTime = newColumnBar.OpenTime,
                    EndTime = newColumnIndex + 1 < columns ? bars[newColumnIndex + 1].OpenTime : newColumnBar.OpenTime.Add(BarTimeSpan),
                    Top = pointToPile.Top,
                    Bottom = pointToPile.Bottom,
                    Color = pointToPile.Color
                };

                matrix[row, newColumnIndex] = piledPoint;
            }

            var pocRowIndex = GetPointOfControlRowIndex(matrix);

            if (matrix[pocRowIndex, 0] == null)
            {
                marketProfileModel.DevelopingPoC.Add(bar.OpenTime, double.NaN);
                marketProfileModel.DevelopingAreaHigh.Add(bar.OpenTime, double.NaN);
                marketProfileModel.DevelopingAreaLow.Add(bar.OpenTime, double.NaN);
                continue;
            }

            var poc = GetPointOfControlPrice(matrix, pocRowIndex);
            var (valueAreaHigh, valueAreaLow) = GetValueArea(matrix, ValueAreaPercentage);

            marketProfileModel.DevelopingPoC.Add(bar.OpenTime, poc);
            if (!double.IsNaN(valueAreaHigh))
                marketProfileModel.DevelopingAreaHigh.Add(bar.OpenTime, valueAreaHigh);

            if (!double.IsNaN(valueAreaLow))
                marketProfileModel.DevelopingAreaLow.Add(bar.OpenTime, valueAreaLow);
        }

        marketProfileModel.Matrix = matrix;
        marketProfileModel.StartTime = startTime;
        marketProfileModel.EndTime = endTime;

        return marketProfileModel;
    }

    /// <summary>
    /// Fills a matrix with the bars' values (it would overlap the bars if you draw this matrix on the chart, little blocks would be drawn)
    /// </summary>
    /// <param name="bars"></param>
    /// <param name="numberOfSlices"></param>
    /// <param name="startColor"></param>
    /// <param name="endColor"></param>
    /// <returns></returns>
    /// <remarks>This is the old method, the new one works better for calculating the developing POC, VAH and VAL</remarks>
    [Obsolete("Use FillAndPileMatrixCalculation instead")]
    public MarketProfileModel FillMatrix(Bar[] bars, int numberOfSlices, Color startColor, Color endColor)
    {
        var rows = numberOfSlices;
        var columns = bars.Length;
        var startTime = bars.First().OpenTime;
        var endTime = bars.Last().OpenTime;
        var histogramSize = GetHistogramSize(bars, numberOfSlices);
        var bottomOfRegion = bars.Select(x => x.Low).Min();
        var colors = Helpers.GenerateColorGradient(startColor, endColor, columns);

        var matrix = new MatrixPoint[rows, columns];

        for (var row = 0; row < rows; row++)
        {
            for (var column = 0; column < columns; column++)
            {
                var bar = bars[column];
                //slice goes from the bottom to the top
                var sliceBottom = bottomOfRegion + histogramSize * row;
                var sliceTop = sliceBottom + histogramSize;

                if (sliceTop < bar.Low || sliceBottom > bar.High)
                    continue;
                
                var point = new MatrixPoint
                {
                    Direction = bar.Close > bar.Open ? Direction.Up : Direction.Down,
                    StartTime = bar.OpenTime,
                    EndTime = column + 1 < columns ? bars[column + 1].OpenTime: bar.OpenTime.Add(BarTimeSpan),
                    Top = sliceBottom + histogramSize,
                    Bottom = sliceBottom,
                    Color = colors[column]
                };
                
                matrix[row, column] = point;
            }
        }

        return new MarketProfileModel
        {
            Matrix = matrix,
            StartTime = startTime,
            EndTime = endTime
        };
    }
    
    /// <summary>
    /// Moves all matrix points to the left, so it looks like a profile
    /// </summary>
    /// <param name="bars"></param>
    /// <param name="matrix"></param>
    /// <returns></returns>
    [Obsolete("We are piling the matrix on every bar now, so this is not needed anymore")]
    // ReSharper disable once UnusedMember.Global
    public MatrixPoint[,] PileMatrixPointsToLeft(Bar[] bars, MatrixPoint[,] matrix)
    {
        var piled = new MatrixPoint[matrix.GetLength(0), matrix.GetLength(1)];

        /// we're traversing each row === of the matrix, getting the values of each column
        for (var rowIndex = 0; rowIndex < matrix.GetLength(0); rowIndex++)
        {
            var columnValues = new List<MatrixPoint>();

            for (var columnIndex = 0; columnIndex < matrix.GetLength(1); columnIndex++)
            {
                var point = matrix[rowIndex, columnIndex];
                
                if (point == null)
                    continue;
                
                columnValues.Add(point);
            }

            for (var newColumnIndex = 0; newColumnIndex < columnValues.Count; newColumnIndex++)
            {
                var rowValue = columnValues[newColumnIndex];
                var bar = bars[newColumnIndex];
                
                var piledPoint = new MatrixPoint
                {
                    Direction = rowValue.Direction,
                    StartTime = bar.OpenTime,
                    EndTime = newColumnIndex + 1 < columnValues.Count ? bars[newColumnIndex + 1].OpenTime : bar.OpenTime.Add(BarTimeSpan),
                    Top = rowValue.Top,
                    Bottom = rowValue.Bottom,
                    Color = rowValue.Color
                };
                
                piled[rowIndex, newColumnIndex] = piledPoint;
            }
        }
        
        return piled;
    }
    
    /// <summary>
    /// Moves the matrix points to the right, so it looks like a profile
    /// </summary>
    /// <param name="bars"></param>
    /// <param name="matrix"></param>
    /// <returns></returns>
    [Obsolete("We are piling the matrix on every bar now, so this is not needed anymore")]
    // ReSharper disable once UnusedMember.Global
    public MatrixPoint[,] PileMatrixPointsToRight(Bar[] bars, MatrixPoint[,] matrix)
    {
        var piled = new MatrixPoint[matrix.GetLength(0), matrix.GetLength(1)];

        for (var rowIndex = 0; rowIndex < matrix.GetLength(0); rowIndex++)
        {
            var columnValues = new List<MatrixPoint>();

            for (var columnIndex = 0; columnIndex < matrix.GetLength(1); columnIndex++)
            {
                var point = matrix[rowIndex, columnIndex];

                if (point == null)
                    continue;

                columnValues.Add(point);
            }

            for (var newColumnIndex = 0; newColumnIndex < columnValues.Count; newColumnIndex++)
            {
                var rowValue = columnValues[newColumnIndex];
                // Place at the rightmost available columns
                var bar = bars[bars.Length - columnValues.Count + newColumnIndex];

                var piledPoint = new MatrixPoint
                {
                    Direction = rowValue.Direction,
                    StartTime = bar.OpenTime,
                    EndTime = newColumnIndex + 1 < columnValues.Count ? bars[newColumnIndex + 1].OpenTime : bar.OpenTime.Add(BarTimeSpan),
                    Top = rowValue.Top,
                    Bottom = rowValue.Bottom,
                    Color = rowValue.Color
                };

                piled[rowIndex, bars.Length - columnValues.Count + newColumnIndex] = piledPoint;
            }
        }

        return piled;
    }
    
    public static double GetPointOfControlPrice(MatrixPoint[,] matrix, int rowIndex)
    {
        return (matrix[rowIndex, 0].Top + matrix[rowIndex, 0].Bottom) / 2.0;
    }
    
    public static int GetPointOfControlRowIndex(MatrixPoint[,] matrix)
    {
        var maxPoints = 0;
        var pointOfControlRow = 0;
        
        for (var row = 0; row < matrix.GetLength(0); row++)
        {
            var point = 0;
            
            for (var column = 0; column < matrix.GetLength(1); column++)
            {
                if (matrix[row, column] == null)
                    continue;

                point++;
            }
            
            if (point > maxPoints)
            {
                pointOfControlRow = row;
                maxPoints = point;
            }
        }

        return pointOfControlRow;
    }

    /// <summary>
    /// The median is the price level at which 50% of the total volume occurred above and 50% occurred below. 
    /// </summary>
    /// <param name="matrix"></param>
    /// <returns></returns>
    public double GetMedianPrice(MatrixPoint[,] matrix)
    {
        //from bottom to top
        var halfOfBlocks = GetTotalBlocks(matrix) / 2;
        var blocks = 0;
        var price = 0.0;

        for (var row = 0; row < matrix.GetLength(0); row++)
        {
            for (var column = 0; column < matrix.GetLength(1); column++)
            {
                if (matrix[row, column] == null)
                    continue;
                
                blocks++;
                price = (matrix[row, column].Top + matrix[row, column].Bottom) / 2.0;
            }
            
            if (blocks >= halfOfBlocks)
                break;
        }

        return price;
    }

    public int GetMedianRowIndex(MatrixPoint[,] matrix)
    {
        //from bottom to top
        var halfOfBlocks = GetTotalBlocks(matrix) / 2;
        var blocks = 0;

        for (var row = 0; row < matrix.GetLength(0); row++)
        {
            for (var column = 0; column < matrix.GetLength(1); column++)
            {
                if (matrix[row, column] == null)
                    continue;
                
                blocks++;
            }

            if (blocks > halfOfBlocks)
                return row;
        }
        
        throw new Exception("Median row not found");
    }
    
    public DateTime GetMedianRowEndTime(MatrixPoint[,] matrix)
    {
        var medianRow = GetMedianRowIndex(matrix);
        var endTimeColumnIndex = 0;

        for (var columnIndex = 0; columnIndex < matrix.GetLength(1); columnIndex++)
        {
            if (matrix[medianRow, columnIndex] == null)
                break;
            
            endTimeColumnIndex = columnIndex;
        }
        
        return matrix[medianRow, endTimeColumnIndex].EndTime;
    }

    public DateTime GetPointOfControlRowEndTime(MatrixPoint[,] matrix)
    {
        var pointOfControlRow = GetPointOfControlRowIndex(matrix);
        var endTimeColumnIndex = 0;
        
        for (var columnIndex = 0; columnIndex < matrix.GetLength(1); columnIndex++)
        {
            if (matrix[pointOfControlRow, columnIndex] == null)
                break;
            
            endTimeColumnIndex = columnIndex;
        }
        
        return matrix[pointOfControlRow, endTimeColumnIndex].EndTime;
    }

    public static (int totalTopBlocks, int totalBottomBlocks) GetValuesAroundPointOfControl(MatrixPoint[,] matrix)
    {
        //from bottom to top
        var topBlocks = 0;
        var bottomBlocks = 0;
        
        var pointOfControlIndex = GetPointOfControlRowIndex(matrix);
        
        if (matrix[pointOfControlIndex, 0] == null)
            return (-1, -1);

        for (var row = 0; row < matrix.GetLength(0); row++)
        {
            if (matrix[row, 0] == null)
                continue;

            var rowTotalTpo = GetTotalRowPoints(matrix, row);

            if (row < pointOfControlIndex)
                bottomBlocks += rowTotalTpo;
            else if (row > pointOfControlIndex) 
                topBlocks += rowTotalTpo;
        }

        return (topBlocks, bottomBlocks);
    }

    public (double vahPrice, double valPrice) GetValueArea(MatrixPoint[,] matrix, double percentage = 0.7)
    {
        var totalBlocks = GetTotalBlocks(matrix);
        var targetBlocks = (int)Math.Round(totalBlocks * percentage);

        var pointOfControlRowIndex = GetPointOfControlRowIndex(matrix);
        var blockCounter = GetTotalRowPoints(matrix, pointOfControlRowIndex);
        var topCounter = pointOfControlRowIndex;
        var bottomCounter = pointOfControlRowIndex;

        while (blockCounter < targetBlocks)
        {
            var canMoveUp = topCounter + 1 < matrix.GetLength(0) && matrix[topCounter + 1, 0] != null;
            var canMoveDown = bottomCounter - 1 >= 0 && matrix[bottomCounter - 1, 0] != null;

            if (!canMoveUp && !canMoveDown)
                break;

            int topRowPoints = 0, bottomRowPoints = 0;

            if (canMoveUp)
            {
                topCounter++;
                topRowPoints = GetTotalRowPoints(matrix, topCounter);
            }
            if (canMoveDown)
            {
                bottomCounter--;
                bottomRowPoints = GetTotalRowPoints(matrix, bottomCounter);
            }

            blockCounter += topRowPoints + bottomRowPoints;
        }

        var vah = matrix[topCounter, 0] != null ? matrix[topCounter, 0].Middle : double.NaN;
        var val = matrix[bottomCounter, 0] != null ? matrix[bottomCounter, 0].Middle : double.NaN;
        return (vah, val);
    }

    public bool IsProminentLine(MatrixPoint[,] matrix, double prominencePercentage)
    {
        var totalBlocks = GetTotalBlocks(matrix);
        var targetBlocks = totalBlocks * prominencePercentage;
        
        var pointOfControlRowIndex = GetPointOfControlRowIndex(matrix);
        var totalPointOfControlBlocks = GetTotalRowPoints(matrix, pointOfControlRowIndex);
        
        return totalPointOfControlBlocks > targetBlocks;
    }

    public static double GetTopPrice(MatrixPoint[,] matrix)
    {
        return matrix[matrix.GetLength(0) - 1, 0].Top;
    }

    public static double GetBottomPrice(MatrixPoint[,] matrix)
    {
        return matrix[0, 0].Bottom;
    }

    public static SinglePrint[] GetSinglePrints(MatrixPoint[,] matrix)
    {
        var singlePrints = new List<SinglePrint>();
        var singlePrintRowIndexes = new List<int>();
        
        for (var row = 0; row < matrix.GetLength(0); row++)
        {
            var columns = 0;
            
            for (var column = 0; column < matrix.GetLength(1); column++)
            {
                if (matrix[row, column] == null)
                    break;
                
                columns++;
            }
            
            if (columns == 1)
            {
                singlePrintRowIndexes.Add(row);
            }
        }

        var groups = Helpers.GroupAdjacent(singlePrintRowIndexes.ToArray());

        foreach (var group in groups)
        {
            var startTime = matrix[group[0], 0].StartTime;
            var endTime = matrix[group[0], 0].EndTime;
            var high = matrix[group[^1], 0].Top;
            var low = matrix[group[0], 0].Bottom;
            var topTpoIndex = group[^1];
            var bottomTpoIndex = group[0];
            
            singlePrints.Add(new SinglePrint(startTime, endTime, high, low, topTpoIndex, bottomTpoIndex));
        }
        
        return singlePrints.ToArray();
    }

    private static int GetTotalBlocks(MatrixPoint[,] matrix)
    {
        var totalBlocks = 0;

        for (var row = 0; row < matrix.GetLength(0); row++)
        for (var column = 0; column < matrix.GetLength(1); column++)
        {
            if (matrix[row, column] == null)
                continue;
                
            totalBlocks++;
        }
        
        return totalBlocks;
    }

    private static int GetTotalRowPoints(MatrixPoint[,] matrix, int row)
    {
        var point = 0;
            
        for (var column = 0; column < matrix.GetLength(1); column++)
        {
            if (matrix[row, column] == null)
                continue;

            point++;
        }
        
        return point;
    }

    #region Resources

    public double ValueAreaPercentage => _resources.ValueAreaPercentage;
    public TimeFrame TimeFrame => _resources.TimeFrame;
    public Bars Bars => _resources.Bars;
    public Symbol Symbol => _resources.Symbol;
    public Chart Chart => _resources.Chart;
    public double OneTickSize => _resources.OneTickSize;
    public void Print(object message) => _resources.Print(message);

    #endregion
}

================================================
FILE: MarketProfile/MarketProfile/Calculator/MatrixPoint.cs
================================================
using System;
using cAlgo.API;

namespace cAlgo;

public class MatrixPoint
{
    public Direction Direction { get; init; }
    public DateTime StartTime { get; init; }
    public DateTime EndTime { get; init; }
    public double Top { get; init; }
    public double Bottom { get; init; }
    public double Middle => (Top + Bottom) / 2.0;
    public Color Color { get; init; }
}

================================================
FILE: MarketProfile/MarketProfile/Enums/AlertCheckBar.cs
================================================

namespace cAlgo;

public enum AlertCheckBar
{
    CheckCurrentBar,
    CheckPreviousBar
}

================================================
FILE: MarketProfile/MarketProfile/Enums/AlertTypes.cs
================================================

namespace cAlgo;

public enum AlertTypes
{
    PriceBreak,
    CandleCloseCrossover,
    GapCrossover
}

================================================
FILE: MarketProfile/MarketProfile/Enums/Direction.cs
================================================
namespace cAlgo;

public enum Direction
{
    Up,
    Down
}

================================================
FILE: MarketProfile/MarketProfile/Enums/PilingMode.cs
================================================
namespace cAlgo;

public enum PilingMode
{
    Left,
    Right
}

================================================
FILE: MarketProfile/MarketProfile/Enums/SatSunSolution.cs
================================================
namespace cAlgo;

public enum SatSunSolution
{
    // Normal sessions
    SaturdaySundayNormalDays,
    IgnoreSaturdaySunday,
    AppendSaturdaySunday
}

================================================
FILE: MarketProfile/MarketProfile/Enums/SessionPeriod.cs
================================================
namespace cAlgo;

public enum SessionPeriod
{
    Daily,
    Weekly,
    Monthly,
    Quarterly,
    Semiannual,
    Annual,
    Intraday,
    Rectangle
}

================================================
FILE: MarketProfile/MarketProfile/Enums/SessionsToDrawRays.cs
================================================
namespace cAlgo;

public enum SessionsToDrawRays
{
    None,
    Previous,
    Current,
    PreviousCurrent,
    // Previous & Current
    AllPrevious,
    // All Previous
    All
}

================================================
FILE: MarketProfile/MarketProfile/Enums/SinglePrintType.cs
================================================
namespace cAlgo;

public enum SinglePrintType
{
    No,
    LeftSide,
    RightSide
}

================================================
FILE: MarketProfile/MarketProfile/Enums/Transitions.cs
================================================
namespace cAlgo;

public enum Transitions
{
    Initialized,
    ChangedByHotkey,
    ChangedByParameter,
}

================================================
FILE: MarketProfile/MarketProfile/Enums/WaysToStopRays.cs
================================================
namespace cAlgo;

public enum WaysToStopRays
{
    StopNoRays,
    StopAllRays,
    StopAllRaysExceptPrevSession,
    StopOnlyPreviousSession
}

================================================
FILE: MarketProfile/MarketProfile/EventArgs/CalculateEventArgs.cs
================================================
using System;

namespace cAlgo;

public class CalculateEventArgs : EventArgs
{
    public bool IsNewBar { get; set; }
    public bool IsLastBar { get; set; }
    public int Index { get; set; }
}

================================================
FILE: MarketProfile/MarketProfile/Helpers.cs
================================================
using System;
using System.Collections.Generic;
using System.Globalization;
using cAlgo.API;

namespace cAlgo;

public static class Helpers
{
    public static Color[] GenerateColorGradient(Color startColor, Color endColor, int numberOfBars)
    {
        if (numberOfBars <= 0)
            throw new ArgumentException("numberOfBars must be positive.");

        var colors = new Color[numberOfBars];
        for (int i = 0; i < numberOfBars; i++)
        {
            double t = numberOfBars == 1 ? 0 : (double)i / (numberOfBars - 1);
            int a = (int)Math.Round(startColor.A + (endColor.A - startColor.A) * t);
            int r = (int)Math.Round(startColor.R + (endColor.R - startColor.R) * t);
            int g = (int)Math.Round(startColor.G + (endColor.G - startColor.G) * t);
            int b = (int)Math.Round(startColor.B + (endColor.B - startColor.B) * t);
            // colors[i] = Color.FromArgb(opacity, r, g, b);
            // users can set the opacity from the parameters
            colors[i] = Color.FromArgb(a, r, g, b);
        }
        return colors;
    }
    
    public static TimeSpan GetBarTimeSpan(TimeFrame timeFrame) =>
        timeFrame.ToString() switch
        {
            "Minute" => TimeSpan.FromMinutes(1),
            "Minute2" => TimeSpan.FromMinutes(2),
            "Minute3" => TimeSpan.FromMinutes(3),
            "Minute4" => TimeSpan.FromMinutes(4),
            "Minute5" => TimeSpan.FromMinutes(5),
            "Minute6" => TimeSpan.FromMinutes(6),
            "Minute7" => TimeSpan.FromMinutes(7),
            "Minute8" => TimeSpan.FromMinutes(8),
            "Minute9" => TimeSpan.FromMinutes(9),
            "Minute10" => TimeSpan.FromMinutes(10),
            "Minute15" => TimeSpan.FromMinutes(15),
            "Minute20" => TimeSpan.FromMinutes(20),
            "Minute30" => TimeSpan.FromMinutes(30),
            "Minute45" => TimeSpan.FromMinutes(45),
            "Hour" => TimeSpan.FromHours(1),
            "Hour2" => TimeSpan.FromHours(2),
            "Hour3" => TimeSpan.FromHours(3),
            "Hour4" => TimeSpan.FromHours(4),
            "Hour6" => TimeSpan.FromHours(6),
            "Hour8" => TimeSpan.FromHours(8),
            "Hour12" => TimeSpan.FromHours(12),
            "Daily" => TimeSpan.FromDays(1),
            "Day2" => TimeSpan.FromDays(2),
            "Day3" => TimeSpan.FromDays(3),
            "Weekly" => TimeSpan.FromDays(7),
            "Monthly" => TimeSpan.FromDays(30),
            _ => throw new ArgumentOutOfRangeException(nameof(timeFrame), timeFrame, "TimeFrame not supported")
        };

    public static List<int[]> GroupAdjacent(int[] input)
    {
        var result = new List<int[]>();
        if (input == null || input.Length == 0)
            return result;

        var group = new List<int> { input[0] };

        for (int i = 1; i < input.Length; i++)
        {
            if (input[i] == input[i - 1] + 1)
            {
                group.Add(input[i]);
            }
            else
            {
                result.Add(group.ToArray());
                group = new List<int> { input[i] };
            }
        }
        result.Add(group.ToArray());
        return result;
    }
    
    public static bool IsInIntradaySession(DateTime time, IntradaySessionDefinition session)
    {
        var timeOfDay = time.TimeOfDay;
        return session.Start <= session.End 
            ? timeOfDay >= session.Start && timeOfDay < session.End
            : timeOfDay >= session.Start || timeOfDay < session.End;
    }
    
    public static bool SameSessionDay(DateTime sessionStart, DateTime barTime)
    {
        // Implement logic to check if two times belong to the same session day
        // This would need to account for market open times
        return sessionStart.Date == barTime.Date;
    }

    public static int GetWeekNumber(DateTime time)
    {
        // Get ISO week number
        return CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(
            time, CalendarWeekRule.FirstDay, DayOfWeek.Monday);
    }

    public static int GetQuarter(DateTime time)
    {
        return (time.Month - 1) / 3 + 1;
    }
}

================================================
FILE: MarketProfile/MarketProfile/Interfaces/IIndicatorResources.cs
================================================
using cAlgo.API;

namespace cAlgo;

public interface IIndicatorResources
{
    TimeFrame TimeFrame { get; }
}

================================================
FILE: MarketProfile/MarketProfile/ManagersAndFeatures/AlertManager.cs
================================================
using System;
using cAlgo.API;
using cAlgo.API.Internals;

namespace cAlgo;

public interface IAlertManagerResources
{
    INotifications Notifications { get; }
    Bars Bars { get; }
    IAccount Account { get; }
    Chart Chart { get; }
    DateTime Time { get; }
    Symbol Symbol { get; }
    string ObjPrefix { get; }
    //--
    bool InputAlertNative { get; }
    SoundType InputAlertSoundType { get; }
    bool InputAlertEmail { get; }
    string InputAlertEmailFrom { get; }
    string InputAlertEmailTo { get; }
    bool InputAlertArrows { get; }
    AlertCheckBar InputAlertCheckBar { get; }
    bool InputAlertForValueArea { get; }
    bool InputAlertForMedian { get; }
    bool InputAlertForSinglePrint { get; }
    bool InputAlertOnPriceBreak { get; }
    bool InputAlertOnCandleClose { get; }
    bool InputAlertOnGapCross { get; }
    Color InputAlertArrowColorPb { get; }
    Color InputAlertArrowColorCc { get; }
    Color InputAlertArrowColorGc { get; }
}

public class AlertManager : IAlertManagerResources
{
    private readonly IAlertManagerResources _resources;
    private DateTime _lastAlertTime = DateTime.MinValue; // For CheckPreviousBar alerts;
    private double _closePrev = double.NaN;             // Previous price value for Price Break alerts.
    private int _arrowsCounter;                      // Counter for naming of alert arrows.
    private DateTime _lastAlertTimeCandleCross = DateTime.MinValue;
    private DateTime _lastAlertTimeGapCross = DateTime.MinValue; // For CheckCurrentBar alerts.

    public AlertManager(IAlertManagerResources resources)
    {
        _resources = resources;
    }

    /// <summary>
    /// Checks all alert conditions and issues alerts if needed.
    /// </summary>
    /// <param name="index"></param>
    public void CheckAlerts(int index)
    {
        // No need to check further if no alert method is chosen.
        if (!InputAlertNative && !InputAlertEmail && !InputAlertArrows)// && !AlertPush)
            return;

        // Skip alerts if alerts are disabled for Median, for Value Area, and for Single Print rays.
        if (!InputAlertForMedian && !InputAlertForValueArea && !InputAlertForSinglePrint)
            return;

        // Skip alerts if no cross type is chosen.
        if (!InputAlertOnPriceBreak && !InputAlertOnCandleClose && !InputAlertOnGapCross)
            return;

        // Skip alerts if only closed bar should be checked and it has already been done.
        if (InputAlertCheckBar == AlertCheckBar.CheckPreviousBar && _lastAlertTime == Bars[index].OpenTime) return;

        // Cycle through rays starts here.
        foreach (var ctl in Chart.FindAllObjects<ChartTrendLine>())
        {
            var objectName = ctl.Name;

            // Skip if it is either a non-ray or if this particular ray shouldn't get alerted.
            if (!(InputAlertForMedian && objectName.Contains("PointOfControlRay") ||
                  (InputAlertForValueArea && (objectName.Contains("ValueAreaRayHigh") || objectName.Contains("ValueAreaRayLow"))) ||
                  (InputAlertForSinglePrint && objectName.Contains("SinglePrintRay") && ctl.Color != Color.Transparent)))
                continue;

            // If everything is fine, go on:

            var level = ctl.Y1; //NormalizeDouble(ObjectGetDouble(ChartID(), object_name, OBJPROP_PRICE1), _Digits);

            // Price breaks, candle closes, and gap crosses using Close[0].
            if (InputAlertCheckBar == AlertCheckBar.CheckCurrentBar)
            {
                if (InputAlertOnPriceBreak) // Price break alerts.
                {
                    if (!double.IsNaN(_closePrev) && ((Bars[index].Close >= level && _closePrev < level) || (Bars[index].Close <= level && _closePrev > level)))
                    {
                        DoAlerts(AlertTypes.PriceBreak, objectName);
                        if (InputAlertArrows) CreateArrowObject($"ArrPB{objectName}", Bars[index].OpenTime, Bars[index].Close, InputAlertArrowColorPb, ChartIconType.Circle);
                    }
                    _closePrev = Bars[index].Close;
                }

                if (InputAlertOnCandleClose) // Candle close alerts.
                {
                    if ((Bars[index].Close >= level && Bars[index - 1].Close < level) || (Bars[index].Close <= level && Bars[index - 1].Close > level))
                    {
                        DoAlerts(AlertTypes.CandleCloseCrossover, objectName);
                        if (InputAlertArrows) CreateArrowObject($"ArrCC{objectName}", Bars[index].OpenTime, Bars[index].Close, InputAlertArrowColorCc, ChartIconType.Square);
                    }
                }

                if (InputAlertOnGapCross) // Gap cross alerts.
                {
                    if ((Bars[index].Open > level && Bars[index - 1].High < level) || (Bars[index].Open < level && Bars[index - 1].Low > level))
                    {
                        DoAlerts(AlertTypes.GapCrossover, objectName);
                        if (InputAlertArrows) CreateArrowObject($"ArrGC{objectName}", Bars[index].OpenTime, level, InputAlertArrowColorGc, ChartIconType.Diamond);
                    }
                }
            }
            // Price breaks (using pre-previous High and previous Close), candle closes, and gap crosses using Close[1].
            else if (InputAlertCheckBar == AlertCheckBar.CheckPreviousBar)
            {
                if (InputAlertOnPriceBreak) // Price break alerts.
                {
                    if ((Bars[index - 1].High >= level && Bars[index - 1].Close < level && Bars[index - 2].Close < level) ||
                        (Bars[index - 1].Low <= level && Bars[index - 1].Close > level && Bars[index - 2].Close > level))
                    {
                        DoAlerts(AlertTypes.PriceBreak, objectName);
                        if (InputAlertArrows) CreateArrowObject($"ArrPB{objectName}", Bars[index - 1].OpenTime, Bars[index - 1].Close, InputAlertArrowColorPb, ChartIconType.Circle);
                    }
                }

                if (InputAlertOnCandleClose) // Candle close alerts.
                {
                    if ((Bars[index - 1].Close >= level && Bars[index - 2].Close < level) || (Bars[index - 1].Close <= level && Bars[index - 2].Close > level))
                    {
                        DoAlerts(AlertTypes.CandleCloseCrossover, objectName);
                        if (InputAlertArrows) CreateArrowObject($"ArrCC{objectName}", Bars[index - 1].OpenTime, Bars[index - 1].Close, InputAlertArrowColorCc, ChartIconType.Square);
                    }
                }

                if (InputAlertOnGapCross) // Gap cross alerts.
                {
                    if ((Bars[index - 1].Low > level && Bars[index - 2].High < level) || (Bars[index - 2].Low > level && Bars[index - 1].High < level))
                    {
                        DoAlerts(AlertTypes.GapCrossover, objectName);
                        if (InputAlertArrows) CreateArrowObject($"ArrGC{objectName}", Bars[index - 1].OpenTime, level, InputAlertArrowColorGc, ChartIconType.Diamond);
                    }
                }

                _lastAlertTime = Bars[index].OpenTime;
            }
        }
    }
    
    /// <summary>
    /// Issues alerts based on the alert type and includes object name in the message.
    /// </summary>
    /// <param name="alertType"></param>
    /// <param name="objectName"></param>
    private void DoAlerts(AlertTypes alertType, string objectName)
    {
        // Price Breaks for Current Bar should not be be checked for LastAlertTime.
        // Candle Close and Gap Cross for Current Bar need to be checked against LastAlertTime.
        // All CheckPreviousBar alerts can use a single LastAlertTime (they either trigger at the start of the bar or not). The actual check is performed in CheckAlerts().
        // Using TimeCurrent() for all CheckCurrentBar alerts.
        // Using Time[0] for all CheckPreviousBar alerts.

        // Check last alert time for Candle Close alert type.
        if (alertType == AlertTypes.CandleCloseCrossover && InputAlertCheckBar == AlertCheckBar.CheckCurrentBar && Time <= _lastAlertTimeCandleCross)
            return;

        // Check last alert time for Gap Cross alert type.
        if (alertType == AlertTypes.GapCrossover && InputAlertCheckBar == AlertCheckBar.CheckCurrentBar && Time <= _lastAlertTimeGapCross)
            return;

        var subject = $"Market Profile: {Symbol.Name} {alertType} on {objectName}";

        if (InputAlertNative)
        {
            Alert(subject);
        }

        if (InputAlertEmail)
        {
            var emailSubject = subject;
            var emailBody = $"{Account.BrokerName} - {Account.Number}\r\n\r\n{subject}";

            Notifications.SendEmail(InputAlertEmailFrom, InputAlertEmailTo, emailSubject, emailBody);
        }

        // Remember that this alert has already been sent. For CheckPreviousBar, this is done in CheckAlerts().
        if (alertType == AlertTypes.CandleCloseCrossover && InputAlertCheckBar == AlertCheckBar.CheckCurrentBar)
            _lastAlertTimeCandleCross = Time;
        else if (alertType == AlertTypes.GapCrossover && InputAlertCheckBar == AlertCheckBar.CheckCurrentBar)
            _lastAlertTimeGapCross = Time;
    }
    
    // Creates an arrow object and sets its properties.
    public void CreateArrowObject(string name, DateTime time, double price, Color colour, ChartIconType type)
    {
        var objName = $"{ObjPrefix}name{_arrowsCounter}";
        _arrowsCounter++;
        Chart.DrawIcon(objName, type, time, price, colour);
    }

    #region Resources

    public void Alert(string alertText)
    {
        Notifications.PlaySound(InputAlertSoundType);
        MessageBox.Show(alertText, "Alert", MessageBoxButton.OK);  
    }
    public INotifications Notifications => _resources.Notifications;
    public Bars Bars => _resources.Bars;
    public IAccount Account => _resources.Account;
    public Chart Chart => _resources.Chart;
    public DateTime Time => _resources.Time;
    public Symbol Symbol => _resources.Symbol;
    public string ObjPrefix => _resources.ObjPrefix;
    //--
    public bool InputAlertNative => _resources.InputAlertNative;
    public SoundType InputAlertSoundType => _resources.InputAlertSoundType;
    public bool InputAlertEmail => _resources.InputAlertEmail;
    public string InputAlertEmailFrom => _resources.InputAlertEmailFrom;
    public string InputAlertEmailTo => _resources.InputAlertEmailTo; 
    public bool InputAlertArrows => _resources.InputAlertArrows;
    public AlertCheckBar InputAlertCheckBar => _resources.InputAlertCheckBar;
    public bool InputAlertForValueArea => _resources.InputAlertForValueArea;
    public bool InputAlertForMedian => _resources.InputAlertForMedian;
    public bool InputAlertForSinglePrint => _resources.InputAlertForSinglePrint;
    public bool InputAlertOnPriceBreak => _resources.InputAlertOnPriceBreak;
    public bool InputAlertOnCandleClose => _resources.InputAlertOnCandleClose;
    public bool InputAlertOnGapCross => _resources.InputAlertOnGapCross;
    public Color InputAlertArrowColorPb => _resources.InputAlertArrowColorPb;
    public Color InputAlertArrowColorCc => _resources.InputAlertArrowColorCc;
    public Color InputAlertArrowColorGc => _resources.InputAlertArrowColorGc;
    public DataSeries Open => Bars.OpenPrices;
    public DataSeries High => Bars.HighPrices;
    public DataSeries Low => Bars.LowPrices;
    public DataSeries Close => Bars.ClosePrices;
    public TimeSeries Times => Bars.OpenTimes;
    public int Index => Bars.Count - 1; 

    #endregion
}

================================================
FILE: MarketProfile/MarketProfile/ManagersAndFeatures/HideRaysFromInvisibleSessionsFeature.cs
================================================
using System.Collections.Generic;
using cAlgo.API;

namespace cAlgo;

public interface IHideRaysFromInvisibleSessionsFeatureResources
{
    Chart Chart { get; }
    Bars Bars { get; }
    public List<MarketProfileSession> Sessions { get; }
}

public class HideRaysFromInvisibleSessionsFeature : IHideRaysFromInvisibleSessionsFeatureResources
{
    private readonly IHideRaysFromInvisibleSessionsFeatureResources _resources;

    public HideRaysFromInvisibleSessionsFeature(IHideRaysFromInvisibleSessionsFeatureResources resources)
    {
        _resources = resources;
    }

    public void Start()
    {
        ChangeRaysVisibility();
        Chart.ScrollChanged += ChartScrollChanged;
        Chart.ZoomChanged += ChartZoomChanged;
    }

    private void ChartZoomChanged(ChartZoomEventArgs obj)
    {
        ChangeRaysVisibility();
    }

    private void ChartScrollChanged(ChartScrollEventArgs obj)
    {
        ChangeRaysVisibility();
    }

    public void ChangeRaysVisibility()
    {
        foreach (var session in Sessions) 
            SetVisibility(session, session.Model.EndTime < Bars.OpenTimes[Chart.FirstVisibleBarIndex]);
    }

    private static void SetVisibility(MarketProfileSession session, bool isHidden)
    {
        foreach (var ray in session.MedianRays)
            ray.IsHidden = isHidden;

        foreach (var ray in session.ValueAreaRays)
            ray.IsHidden = isHidden;
    }

    public Chart Chart => _resources.Chart;
    public Bars Bars => _resources.Bars;

    public List<MarketProfileSession> Sessions => _resources.Sessions;
}

================================================
FILE: MarketProfile/MarketProfile/ManagersAndFeatures/HotkeyStateManager.cs
================================================
using System;
using System.Collections.Generic;
using cAlgo.API;
using cAlgo.API.Internals;

namespace cAlgo;

public interface IHotkeyStateManagerResources
{
    Chart Chart { get; }
    void Print(object message);
    TimeFrame TimeFrame { get; }
    SessionState SessionState { get; set; }
    SessionChangeManager SessionChangeManager { get; }
    List<MarketProfileSession> Sessions { get; }
    INotifications Notifications { get; }
    MarketProfileRenderer Renderer { get; }
    
    bool InputSeamlessScrollingMode { get; }
    int InputSessionsToCount { get; }
    bool InputDisableAlertsOnWrongTimeframes { get; }
    SoundType InputAlertSoundType { get; }
    
    void CheckZeroIntradaySessions();
    bool SetSessionsAndAddMarketProfiles(int sessionsToCount, DateTime? endAt = null);
    
    //--
    string InputHotkeyDaily { get; }
    string InputHotkeyWeekly { get; }
    string InputHotkeyMonthly { get; }
    string InputHotkeyQuarterly { get; }
    string InputHotkeySemiannual { get; }
    string InputHotkeyAnnual { get; }
    string InputHotkeyIntraday { get; }
    string InputHotkeyRectangle { get; }
}

public class HotkeyStateManager : IHotkeyStateManagerResources
{
    private readonly IHotkeyStateManagerResources _resources;

    public HotkeyStateManager(IHotkeyStateManagerResources resources)
    {
        _resources = resources;
    }

    public void AddHotkeys()
    {
        Chart.AddHotkey(() => SwitchSessionTo(SessionPeriod.Daily), InputHotkeyDaily);
        Chart.AddHotkey(() => SwitchSessionTo(SessionPeriod.Weekly), InputHotkeyWeekly);
        Chart.AddHotkey(() => SwitchSessionTo(SessionPeriod.Monthly), InputHotkeyMonthly);
        Chart.AddHotkey(() => SwitchSessionTo(SessionPeriod.Quarterly), InputHotkeyQuarterly);
        Chart.AddHotkey(() => SwitchSessionTo(SessionPeriod.Semiannual), InputHotkeySemiannual);
        Chart.AddHotkey(() => SwitchSessionTo(SessionPeriod.Annual), InputHotkeyAnnual);
        Chart.AddHotkey(() => SwitchSessionTo(SessionPeriod.Intraday), InputHotkeyIntraday);
        Chart.AddHotkey(() => SwitchSessionTo(SessionPeriod.Rectangle), InputHotkeyRectangle);   
    }
    
    private void SwitchSessionTo(SessionPeriod sessionPeriod)
    {
        if (sessionPeriod == SessionPeriod.Rectangle && InputSeamlessScrollingMode)
            throw new ArgumentException("Seamless scrolling mode doesn't work with Rectangle sessions.");
        
        if (sessionPeriod == SessionState.LastSessionState)
        {
            Alert($"Session is already set to {sessionPeriod}.");
            return;
        }
        
        Print($"Switching session to {sessionPeriod}.");
        
        if (!SessionChangeManager.CheckSessions(sessionPeriod))
            return;

        SessionChangeManager.UpdateSessionStateTo(sessionPeriod);
        
        foreach (var session in Sessions)
            Renderer.DeleteAllFromSession(session);

        SetSessionsAndAddMarketProfiles(InputSessionsToCount);
    }

    public void Alert(string alertText)
    {
        if (InputDisableAlertsOnWrongTimeframes)
        {
            Print($"Initialization failed: {alertText}");
        }
        else
        {
            Notifications.PlaySound(InputAlertSoundType);
            MessageBox.Show(alertText, "Alert", MessageBoxButton.OK);   
        }
    }
    
    #region MyRegion

    public Chart Chart => _resources.Chart;
    public SessionState SessionState
    {
        get => _resources.SessionState;
        set => _resources.SessionState = value;
    }
    public void Print(object message) => _resources.Print(message);
    public TimeFrame TimeFrame => _resources.TimeFrame;
    public SessionChangeManager SessionChangeManager => _resources.SessionChangeManager;
    public List<MarketProfileSession> Sessions => _resources.Sessions;
    public INotifications Notifications => _resources.Notifications;
    public MarketProfileRenderer Renderer => _resources.Renderer;
    public bool InputSeamlessScrollingMode => _resources.InputSeamlessScrollingMode;
    public int InputSessionsToCount => _resources.InputSessionsToCount;
    public bool InputDisableAlertsOnWrongTimeframes => _resources.InputDisableAlertsOnWrongTimeframes;
    public SoundType InputAlertSoundType => _resources.InputAlertSoundType;
    public void CheckZeroIntradaySessions() => 
        _resources.CheckZeroIntradaySessions();
    public bool SetSessionsAndAddMarketProfiles(int sessionsToCount, DateTime? endAt = null) => 
        _resources.SetSessionsAndAddMarketProfiles(sessionsToCount, endAt);
    public string InputHotkeyDaily => _resources.InputHotkeyDaily;
    public string InputHotkeyWeekly => _resources.InputHotkeyWeekly;
    public string InputHotkeyMonthly => _resources.InputHotkeyMonthly;
    public string InputHotkeyQuarterly => _resources.InputHotkeyQuarterly;
    public string InputHotkeySemiannual => _resources.InputHotkeySemiannual;
    public string InputHotkeyAnnual => _resources.InputHotkeyAnnual;
    public string InputHotkeyIntraday => _resources.InputHotkeyIntraday;
    public string InputHotkeyRectangle => _resources.InputHotkeyRectangle;

    #endregion
}

================================================
FILE: MarketProfile/MarketProfile/ManagersAndFeatures/NewHighLowManager.cs
================================================
using System;
using cAlgo.API;

namespace cAlgo;

public class NewHighLowEventArgs : EventArgs
{
    public double Value { get; set; }
}

public interface INewHighLowManagerResources
{
    event EventHandler<CalculateEventArgs> CalculateEvent;
    Bars Bars { get; }
}

/// <summary>
/// Has events to handle when the last bar is making a new high/low
/// </summary>
public class NewHighLowManager : INewHighLowManagerResources
{
    private readonly INewHighLowManagerResources _resources;
    
    private double _lastHigh = double.NaN;
    private double _lastLow = double.NaN;
    
    public event EventHandler<NewHighLowEventArgs> NewHighOnLastBar;
    public event EventHandler<NewHighLowEventArgs> NewLowOnLastBar;

    public NewHighLowManager(INewHighLowManagerResources resources)
    {
        _resources = resources;
        
        CalculateEvent += NewHighLowFeature_CalculateEvent;
    }

    private void NewHighLowFeature_CalculateEvent(object sender, CalculateEventArgs e)
    {
        if (!e.IsLastBar)
            return;
        
        var index = e.Index;

        if (e.IsNewBar)
        {
            _lastHigh = High[index];
            _lastLow = Low[index];
        }
        else
        {
            if (High[index] > _lastHigh)
            {
                _lastHigh = High[index];
                NewHighOnLastBar?.Invoke(this, new NewHighLowEventArgs { Value = High[index] });
            }

            if (Low[index] < _lastLow)
            {
                _lastLow = Low[index];
                NewLowOnLastBar?.Invoke(this, new NewHighLowEventArgs { Value = Low[index] });
            }   
        }
    }

    public event EventHandler<CalculateEventArgs> CalculateEvent
    {
        add => _resources.CalculateEvent += value;
        remove => _resources.CalculateEvent -= value;
    }

    public Bars Bars => _resources.Bars;
    public DataSeries Open => Bars.OpenPrices;
    public DataSeries High => Bars.HighPrices;
    public DataSeries Low => Bars.LowPrices;
    public DataSeries Close => Bars.ClosePrices;
    public TimeSeries Times => Bars.OpenTimes;
    public int Index => Bars.Count - 1; 
}

================================================
FILE: MarketProfile/MarketProfile/ManagersAndFeatures/OutputDevelopingValuesManager.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using cAlgo.API;

namespace cAlgo;

public interface IOutputDevelopingValuesManagerResources
{
    Chart Chart { get; }
    Bars Bars { get; }
    TimeFrame TimeFrame { get; }
    //--
    int InputOutputDevelopingSessionsToCount { get; }
    int InputValueAreaPercentage { get; }
    Color InputStartColor { get; }
    Color InputEndColor { get; }
    bool InputEnableDevelopingPoC { get; }
    bool InputEnableDevelopingValueAtHighValueAtLow { get; }
    MarketProfileCalculator Calculator { get; }
    MarketProfileRenderer Renderer { get; }
    SessionState SessionState { get; }
    List<IntradaySessionDefinition> IntradaySessions { get; }
    double OneTickSize { get; }
    bool IsNewSessionRequired(MarketProfileSession lastSession, DateTime newBarTime);
    //--
    IndicatorDataSeries OutputDevelopingPoC { get; }
    IndicatorDataSeries OutputDevelopingVah { get; }
    IndicatorDataSeries OutputDevelopingVaL { get; }
}

public class OutputDevelopingValuesManager : 
    IOutputDevelopingValuesManagerResources,
    IIndicatorResources,
    IRenderingModesResources
{
    private readonly IOutputDevelopingValuesManagerResources _resources;
    private readonly IRenderingModesResources _renderingModesResources;

    public List<MarketProfileSession> OutputSessions { get; } = new();

    public OutputDevelopingValuesManager(IOutputDevelopingValuesManagerResources resources, IRenderingModesResources renderingModesResources)
    {
        _resources = resources;
        _renderingModesResources = renderingModesResources;

        // Chart.ScrollChanged += ChartScrollChanged;
        // Chart.ZoomChanged += ChartZoomChanged;
        Bars.BarOpened += OnBarOpened;
    }

    public void SetOutputSessions(int sessionsToCount)
    {
        //var firstVisibleIndex = Chart.FirstVisibleBarIndex;
        var lastVisibleIndex = Chart.LastVisibleBarIndex;
        //var indexRange = lastVisibleIndex - firstVisibleIndex;

        //var renderingStartIndex = (int)Math.Max(0, (firstVisibleIndex - indexRange * 0.5));
        var renderingStartIndex = 0;
        var renderingEndIndex = lastVisibleIndex;

        var renderingStartTime = Bars.OpenTimes[renderingStartIndex];
        var renderingEndTime = Bars.OpenTimes[renderingEndIndex];

        var bars = Bars.Where(x => x.OpenTime >= renderingStartTime && x.OpenTime <= renderingEndTime).ToArray();
        
        var strategy = SessionState.LastSessionState  switch
        {
            SessionPeriod.Daily => SessionProfileStrategyFactory.CreateDaily(this, this),
            SessionPeriod.Weekly => SessionProfileStrategyFactory.CreateWeekly(this, this),
            SessionPeriod.Monthly => SessionProfileStrategyFactory.CreateMonthly(this, this),
            SessionPeriod.Quarterly => SessionProfileStrategyFactory.CreateQuarterly(this, this),
            SessionPeriod.Semiannual => SessionProfileStrategyFactory.CreateSemiannual(this, this),
            SessionPeriod.Annual => SessionProfileStrategyFactory.CreateAnnual(this, this),
            SessionPeriod.Intraday => SessionProfileStrategyFactory.CreateIntradaySessions(IntradaySessions, this, this),
            SessionPeriod.Rectangle => null,
            _ => null
        };
        
        if (strategy == null) 
            return;

        var ranges = sessionsToCount == 0
            ? strategy.GetSessionRanges(Bars, InputStartColor, InputEndColor, renderingStartTime, renderingEndTime).ToArray()
            : strategy.GetSessionRanges(Bars, sessionsToCount, InputStartColor, InputEndColor).ToArray();
        
        for (var index = 0; index < ranges.Length; index++)
        {
            var range = ranges[index];
            var session = new MarketProfileSession
            {
                Range = range,
            };

            var rangeBars = range.Bars.ToArray();
            
            if (bars.Length == 0)
                continue;
            
            var highMinusLow = bars.Max(x => x.High) - bars.Min(x => x.Low);
            var slices = (int) (highMinusLow / OneTickSize);

            var profileModel = Calculator.FillAndPileMatrixCalculation(rangeBars, slices, range.StartColor, range.EndColor);
            session.Model = profileModel;
            
            if (InputEnableDevelopingPoC)
                Renderer.RenderDevelopingPoc(profileModel.DevelopingPoC);
            
            if (InputEnableDevelopingValueAtHighValueAtLow)
            {
                Renderer.RenderDevelopingVahs(profileModel.DevelopingAreaHigh);
                Renderer.RenderDevelopingVals(profileModel.DevelopingAreaLow);   
            }
            
            OutputSessions.Add(session);
        }
    }
    
    private void OnBarOpened(BarOpenedEventArgs obj)
    {
        if (SessionState.LastSessionState == SessionPeriod.Rectangle)
            return;
        
        var lastSession = OutputSessions.LastOrDefault();
        
        if (lastSession == null)
        {
            SetOutputSessions(1);
            return;
        }
        
        var parsed = DateTime.TryParse(InputStartFromDate, out var startFrom);

        if (parsed && !InputStartFromCurrentSession)
            return;

        var needNewSession = IsNewSessionRequired(lastSession, Bars.LastBar.OpenTime);

        if (needNewSession)
            SetOutputSessions(1);
        else
            UpdateLastOutputSession(session: lastSession);
        
        //
        // ClearOutputsFromSession(lastSession);
        // OutputSessions.Remove(lastSession);
        // SetOutputSessions(1);
    }

    private void UpdateLastOutputSession(MarketProfileSession session)
    {
        var strategy = SessionState.LastSessionState switch
        {
            SessionPeriod.Daily => SessionProfileStrategyFactory.CreateDaily(this, this),
            SessionPeriod.Weekly => SessionProfileStrategyFactory.CreateWeekly(this, this),
            SessionPeriod.Monthly => SessionProfileStrategyFactory.CreateMonthly(this, this),
            SessionPeriod.Quarterly => SessionProfileStrategyFactory.CreateQuarterly(this, this),
            SessionPeriod.Semiannual => SessionProfileStrategyFactory.CreateSemiannual(this, this),
            SessionPeriod.Annual => SessionProfileStrategyFactory.CreateAnnual(this, this),
            SessionPeriod.Intraday => SessionProfileStrategyFactory.CreateIntradaySessions(IntradaySessions, this, this),
            SessionPeriod.Rectangle => null,
            _ => null
        };

        if (strategy == null) 
            return;
        
        var ranges = strategy.GetSessionRanges(Bars, 1, InputStartColor, InputEndColor).ToArray();
        
        //there should be only one range
        if (ranges.Length != 1)
            throw new Exception("There should be only one range");

        if (session.Range.Start != ranges[0].Start)
            return;

        session.Range = ranges[0];
        
        if (!session.Range.Bars.Any())
        {
            // Skip this session or handle empty range
            //Print($"Warning: No bars found for session range {session.Range.Start} to {session.Range.End}");
            return; // or continue to next session
        }
        
        var bars = session.Range.Bars.ToArray();
        
        var highMinusLow = bars.Max(x => x.High) - bars.Min(x => x.Low);
        var slices = (int) (highMinusLow / OneTickSize);

        if (slices == 0)
        {
            //Print($"Slices is 0 | This can't be processed Bars is {bars.Length} (High {bars.Max(x => x.High)} | Low {bars.Min(x => x.Low)} Minus Low is {highMinusLow}) | OneTickSize is {OneTickSize}");
            return;
        }
        
        var profileModel = Calculator.FillAndPileMatrixCalculation(bars, slices, session.Range.StartColor, session.Range.EndColor);
        
        //these two below could be totally removed and reference the latest "developing" values
        var (vah, val) = Calculator.GetValueArea(profileModel.Matrix, InputValueAreaPercentage / 100.0);
        var pointOfControlRowIndex = MarketProfileCalculator.GetPointOfControlRowIndex(profileModel.Matrix);

        if (profileModel.Matrix[pointOfControlRowIndex, 0] == null)
        {
            //Print($"Unable to calculate POC because the row is null");
            return;
        }
        
        session.Model = profileModel;
            
        if (InputEnableDevelopingPoC)
            Renderer.RenderDevelopingPoc(profileModel.DevelopingPoC);
            
        if (InputEnableDevelopingValueAtHighValueAtLow)
        {
            Renderer.RenderDevelopingVahs(profileModel.DevelopingAreaHigh);
            Renderer.RenderDevelopingVals(profileModel.DevelopingAreaLow);   
        }
    }

    public void ClearOutputsFromSession(MarketProfileSession session)
    {
        var sessionStartIndex = Bars.OpenTimes.GetIndexByTime(session.Range.Start);
        var sessionEndIndex = Bars.OpenTimes.GetIndexByTime(session.Range.End);

        for (var i = sessionStartIndex; i <= sessionEndIndex; i++)
        {
            if (!double.IsNaN(OutputDevelopingPoC[i]))
                OutputDevelopingPoC[i] = double.NaN;

            if (!double.IsNaN(OutputDevelopingVah[i]))
                OutputDevelopingVah[i] = double.NaN;

            if (!double.IsNaN(OutputDevelopingVaL[i]))
                OutputDevelopingVaL[i] = double.NaN;
        }
    }

    #region Outputs

    public Chart Chart => _resources.Chart;
    public Bars Bars => _resources.Bars;
    public TimeFrame TimeFrame => _resources.TimeFrame;
    public int InputOutputDevelopingSessionsToCount => _resources.InputOutputDevelopingSessionsToCount;
    public int InputValueAreaPercentage => _resources.InputValueAreaPercentage;
    public Color InputStartColor => _resources.InputStartColor;
    public Color InputEndColor => _resources.InputEndColor;
    public bool InputEnableDevelopingPoC => _resources.InputEnableDevelopingPoC;
    public bool InputEnableDevelopingValueAtHighValueAtLow => _resources.InputEnableDevelopingValueAtHighValueAtLow;
    public MarketProfileCalculator Calculator => _resources.Calculator;
    public MarketProfileRenderer Renderer => _resources.Renderer;
    public SessionState SessionState => _resources.SessionState;
    public List<IntradaySessionDefinition> IntradaySessions => _resources.IntradaySessions;
    public double OneTickSize => _resources.OneTickSize;
    public bool IsNewSessionRequired(MarketProfileSession lastSession, DateTime newBarTime) => 
        _resources.IsNewSessionRequired(lastSession, newBarTime);

    public IndicatorDataSeries OutputDevelopingPoC => _resources.OutputDevelopingPoC;
    public IndicatorDataSeries OutputDevelopingVah => _resources.OutputDevelopingVah;
    public IndicatorDataSeries OutputDevelopingVaL => _resources.OutputDevelopingVaL;
    public string InputStartFromDate => _renderingModesResources.InputStartFromDate;
    public bool InputStartFromCurrentSession => _renderingModesResources.InputStartFromCurrentSession;
    public bool InputSeamlessScrollingMode => _renderingModesResources.InputSeamlessScrollingMode;
    public SatSunSolution InputSaturdaySunday => _renderingModesResources.InputSaturdaySunday;

    #endregion
}

================================================
FILE: MarketProfile/MarketProfile/ManagersAndFeatures/SeamlessScrollingManager.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using cAlgo.API;

namespace cAlgo;

public interface ISeamlessScrollingManagerResources
{
    Bars Bars { get; }
    Chart Chart { get; }
    bool SetSessionsAndAddMarketProfiles(int sessionsToCount, DateTime? endAt = null);
    MarketProfileRenderer Renderer { get; }
    List<MarketProfileSession> Sessions { get; }
    SessionState SessionState { get; }
    int InputSessionsToCount { get; }
    void Print(object message);
    void Sleep(TimeSpan timeSpan);
}

public class SeamlessScrollingManager : ISeamlessScrollingManagerResources
{
    //--to do seamless scrolling I need to:
    //Check we're not in Rectangle Mode (just in case)
    //Check if the last MarketProfileSession time is not visible in the current screen
    //If it is not visible
    //I delete everything
    //Render them again
    //But the bars used for rendering have changed, the latest bar is the one visible
    
    private readonly ISeamlessScrollingManagerResources _resources;

    public SeamlessScrollingManager(ISeamlessScrollingManagerResources resources)
    {
        _resources = resources;
        
        // var button = new Button
        // {
        //     Text = "Seamless Scrolling Mode",
        //     HorizontalAlignment = HorizontalAlignment.Center,
        //     VerticalAlignment = VerticalAlignment.Bottom,
        //     Width = 200,
        //     Height = 50,
        //     FontSize = 12,
        // };
        //
        // button.Click += _ => ResetSessions();
        //
        // Chart.AddControl(button);
        //
        // var justDeleteButton = new Button
        // {
        //     Text = "Delete All",
        //     HorizontalAlignment = HorizontalAlignment.Right,
        //     VerticalAlignment = VerticalAlignment.Bottom,
        //     Width = 200,
        //     Height = 50,
        //     FontSize = 12,
        // };
        //
        // justDeleteButton.Click += _ =>
        // {
        //     DeleteAllSessions();
        //     Sessions.Clear();
        // };
        //
        // Chart.AddControl(justDeleteButton);
    }

    public void Start()
    {
        Chart.ScrollChanged += _ => ResetSessions();
        //Chart.ZoomChanged += _ => ResetSessions();
        //Chart.SizeChanged += _ => ResetSessions();
    }

    private void ResetSessions()
    {
        if (SessionState.LastSessionState == SessionPeriod.Rectangle)
            return;
        
        //Sleeping so that it doesn't redraw immediately and slows down the process by doing this too often
        Sleep(TimeSpan.FromSeconds(1));
        
        var lastSessionStartTime = Sessions.LastOrDefault()?.Range.Start;

        //In other words, if the date of the last session is visible, don't delete it
        if (!lastSessionStartTime.HasValue)
            return;

        // if (lastSessionStartTime.Value >= Bars.OpenTimes[Chart.LastVisibleBarIndex] ||
        //     lastSessionStartTime.Value < Bars.OpenTimes[Chart.FirstVisibleBarIndex])
        // {
        //
        // }
        
        Renderer.DeleteAllSessions(Sessions);
        Sessions.Clear();
        SetSessionsAndAddMarketProfiles(InputSessionsToCount, Bars.OpenTimes[Chart.LastVisibleBarIndex]);
    }

    public Bars Bars => _resources.Bars;

    public Chart Chart => _resources.Chart;
    public bool SetSessionsAndAddMarketProfiles(int sessionsToCount, DateTime? endAt = null) => 
        _resources.SetSessionsAndAddMarketProfiles(sessionsToCount,endAt);
    public MarketProfileRenderer Renderer => _resources.Renderer;

    public List<MarketProfileSession> Sessions => _resources.Sessions;
    public SessionState SessionState => _resources.SessionState;
    public int InputSessionsToCount => _resources.InputSessionsToCount;
    public void Print(object message) => _resources.Print(message);
    public void Sleep(TimeSpan timeSpan)
    {
        _resources.Sleep(timeSpan);
    }
}

================================================
FILE: MarketProfile/MarketProfile/ManagersAndFeatures/SessionChangeManager.cs
================================================
using System;
using cAlgo.API;

namespace cAlgo;

public interface ISessionChangeManagerResources
{
    LocalStorage LocalStorage { get; }
    SessionState SessionState { get; set; }
    SessionPeriod InputSession { get; }
    bool InputSeamlessScrollingMode { get; }
    string ObjPrefix { get; }
    string StoragePrefix { get; }
    int TpoSize { get; }
    TimeFrame ResolvedTPOTimeframe { get; }
    Chart Chart { get; }
    TimeFrame TimeFrame { get; }
    void Print(object message);
}

public class SessionChangeManager : ISessionChangeManagerResources
{
    private readonly ISessionChangeManagerResources _resources;

    public SessionChangeManager(ISessionChangeManagerResources resources)
    {
        _resources = resources;
    }

    public void UpdateSessionStateTo(SessionPeriod sessionPeriod)
    {
        SessionState.LastSessionState = sessionPeriod;
        SessionState.LastTransition = Transitions.ChangedByHotkey;
        LocalStorage.SetObject(StoragePrefix + "SessionState", SessionState);
        LocalStorage.Flush(LocalStorageScope.Instance);
    }

    public void LoadSessionState()
    {
        var sessionState = LocalStorage.GetObject<SessionState>(StoragePrefix + "SessionState", LocalStorageScope.Instance);

        //- There's no storage for this instance
        //  - The indicator is initialized with the values from the parameter
        if (sessionState == null)
        {
            Print($"No storage for this instance. Initializing with the values from the parameter.");

            SessionState = new SessionState
            {
                LastSessionState = InputSession,
                LastSessionStateByParameter = InputSession,
                LastTransition = Transitions.Initialized
            };

            return;
        }

        SessionState = new SessionState();

        //  - The input-parameter has changed from last run, SessionState needs to be updated
        //    with the current input-parameter, this change takes priority over the hotkey-state change
        if (InputSession != sessionState.LastSessionStateByParameter)
        {
            Print($"The input-parameter has changed from last run, SessionState will be changed from {sessionState.LastSessionStateByParameter} to {InputSession}.");

            SessionState.LastSessionState = InputSession;
            SessionState.LastSessionStateByParameter = InputSession;
            SessionState.LastTransition = Transitions.ChangedByParameter;
        }
        //The input-parameter has not changed from last run
        else
        {
            Print($"The input-parameter has not changed from last run");

            //if a hotkey was used, SessionState needs to be updated with the last hotkey-state used
            //since I'm loading from the file, no need to change anything
            if (sessionState.LastTransition == Transitions.ChangedByHotkey)
            {
                Print($"A Hotkey was used, Session State will be changed to {SessionState.LastSessionState}.");

                SessionState = new SessionState
                {
                    LastSessionState = sessionState.LastSessionState,
                    LastSessionStateByParameter = sessionState.LastSessionStateByParameter,
                    LastTransition = Transitions.ChangedByHotkey
                };
            }
            //Also nothing to change here
            //if a hotkey was not used, SessionState and input-parameter should be the same,
            //no need to update anything
            else
            {
                Print($"No hotkey was used, SessionState will be assigned {sessionState.LastSessionState}.");

                SessionState = new SessionState
                {
                    LastSessionState = sessionState.LastSessionState,
                    LastSessionStateByParameter = sessionState.LastSessionStateByParameter,
                    LastTransition = sessionState.LastTransition
                };
            }
        }
    }
    
    /// <summary>
    /// Returns true if it can draw the session
    /// But also handles if the TimeFrame needs to be changed 
    /// </summary>
    /// <param name="sessionPeriod"></param>
    /// <returns></returns>
    /// <exception cref="ArgumentOutOfRangeException"></exception>
    public bool CheckSessions(SessionPeriod sessionPeriod) =>
        sessionPeriod switch
        {
            SessionPeriod.Daily => CheckSession(sessionPeriod, TimeFrame.Minute5, TimeFrame.Minute30),
            SessionPeriod.Weekly => CheckSession(sessionPeriod, TimeFrame.Minute30, TimeFrame.Hour4),
            SessionPeriod.Monthly => CheckSession(sessionPeriod, TimeFrame.Hour, TimeFrame.Daily),
            SessionPeriod.Quarterly => CheckSession(sessionPeriod, TimeFrame.Hour4, TimeFrame.Daily),
            SessionPeriod.Semiannual => CheckSession(sessionPeriod, TimeFrame.Hour4, TimeFrame.Weekly),
            SessionPeriod.Annual => CheckSession(sessionPeriod, TimeFrame.Hour4, TimeFrame.Weekly),
            SessionPeriod.Intraday => CheckSession(sessionPeriod),
            SessionPeriod.Rectangle => CheckSession(sessionPeriod),
            _ => true
        };

    private bool CheckSession(SessionPeriod sessionPeriod)
    {
        var effectiveTf = TpoSize > 1 ? ResolvedTPOTimeframe : TimeFrame;
        
        if (effectiveTf <= TimeFrame.Minute30) 
            return true;
        
        if (TpoSize > 1)
        {
            MessageBox.Show($"TPO Timeframe should not be higher than M30 for {sessionPeriod} sessions. Please change the TPO Timeframe parameter.", "Alert", MessageBoxButton.OK);
            return false;
        }
        
        var result = MessageBox.Show($"Timeframe should not be higher than M30 for an {sessionPeriod} sessions, do you want to change it?", "Alert", MessageBoxButton.YesNo);
    
        if (result == MessageBoxResult.Yes)
        {
            UpdateSessionStateTo(sessionPeriod);
            Chart.TryChangeTimeFrame(TimeFrame.Minute30);
        }
        
        return false;
    }

    public bool CheckSession(SessionPeriod sessionPeriod, TimeFrame lowerTf, TimeFrame upperTf)
    {
        var effectiveTf = TpoSize > 1 ? ResolvedTPOTimeframe : TimeFrame;
        
        if (effectiveTf >= lowerTf && effectiveTf <= upperTf) 
            return true;
        
        if (TpoSize > 1)
        {
            MessageBox.Show($"TPO Timeframe should be between {lowerTf} and {upperTf} for a {sessionPeriod} session. Please change the TPO Timeframe parameter.", "Alert", MessageBoxButton.OK);
            return false;
        }
        
        var result = MessageBox.Show($"Timeframe should be between {lowerTf} and {upperTf} for a {sessionPeriod} session, do you want to change it?", "Alert", MessageBoxButton.YesNo);

        if (result == MessageBoxResult.Yes)
        {
            UpdateSessionStateTo(sessionPeriod);
            Chart.TryChangeTimeFrame(TimeFrame < lowerTf ? lowerTf : upperTf);
        }
        
        return false;
    }

    public LocalStorage LocalStorage => _resources.LocalStorage;
    public SessionState SessionState
    {
        get => _resources.SessionState;
        set => _resources.SessionState = value;
    }

    public string ObjPrefix => _resources.ObjPrefix;
    public string StoragePrefix => _resources.StoragePrefix;
    public int TpoSize => _resources.TpoSize;
    public TimeFrame ResolvedTPOTimeframe => _resources.ResolvedTPOTimeframe;
    public TimeFrame TimeFrame => _resources.TimeFrame;

    public void Print(object message) => _resources.Print(message);
    public SessionPeriod InputSession => _resources.InputSession;

    public bool InputSeamlessScrollingMode => _resources.InputSeamlessScrollingMode;

    public Chart Chart => _resources.Chart;
}

================================================
FILE: MarketProfile/MarketProfile/MarketProfile.cs
================================================
// -------------------------------------------------------------------------------
//   
// Displays the Market Profile indicator for intraday, daily, weekly, monthly, quarterly, semiannual, annual, and free-form trading sessions.
// Daily - should be attached to M5-M30 timeframes. M30 is recommended.
// Weekly - should be attached to M30-H4 timeframes. H1 is recommended.
// Weeks start on Sunday.
// Monthly - should be attached to H1-D1 timeframes. H4 is recommended.
// Quarterly - should be attached to H4-D1 timeframes. D1 is recommended.
// Semiannual - should be attached to H4-W1 timeframes. D1 is recommended.
// Annual - should be attached to H4-W1 timeframes. D1 is recommended.
// Intraday - should be attached to M1-M30 timeframes. M5 is recommended.
// Designed for major currency pairs, but should work also with exotic pairs, CFDs, or commodities.
//   
// Version 1.25
// Copyright 2010-2026, EarnForex.com
// https://www.earnforex.com/indicators/MarketProfile/
// -------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using cAlgo.API;
using static cAlgo.Helpers;

namespace cAlgo;

[Indicator(AccessRights = AccessRights.FullAccess, IsOverlay = true)]
public class MarketProfile : Indicator,
    IMarketProfileResources,
    IIndicatorResources,
    IMarketProfileRendererSettings,
    ISessionChangeManagerResources,
    IHotkeyStateManagerResources,
    ISeamlessScrollingManagerResources,
    IAlertManagerResources,
    IOutputDevelopingValuesManagerResources,
    IHideRaysFromInvisibleSessionsFeatureResources,
    IRenderingModesResources,
    INewHighLowManagerResources,
    IRectangleModeProcessorResources
{
    #region Parameters
    
    #region Main
    
    [Parameter("Session", DefaultValue = SessionPeriod.Daily, Group = "Main")]
    public SessionPeriod InputSession { get; set; }
    
    [Parameter("Start From Date: Lower Priority.", DefaultValue = "", Group = "Main")]
    public string InputStartFromDate { get; set; }
    
    [Parameter("Start From Current Session: Higher Priority.", DefaultValue = true, Group = "Main")]
    public bool InputStartFromCurrentSession { get; set; }
    
    [Parameter("Sessions To Count: Number of sessions to count Market Profile.", DefaultValue = 2, Group = "Main")]
    public int InputSessionsToCount { get; set; }
    
    public int InputOutputDevelopingSessionsToCount => InputSessionsToCount;
    
    [Parameter("Seamless Scrolling Mode: Show Sessions on Current Screen.", DefaultValue = false, Group = "Main")]
    public bool InputSeamlessScrollingMode { get; set; }
    
    [Parameter("Enable Developing POC.", DefaultValue = false, Group = "Main")]
    public bool InputEnableDevelopingPoC { get; set; }
    
    [Parameter("Enable Developing VAH/VAL.", DefaultValue = false, Group = "Main")]
    public bool InputEnableDevelopingValueAtHighValueAtLow { get; set; }
    
    [Parameter("ValueAreaPercentage: Percentage of TPO's inside Value Area.", DefaultValue = 70, Group = "Main")]
    public int InputValueAreaPercentage { get; set; }
    
    [Parameter("InstancePrefix: prefix for objects to allow multiple instances.", DefaultValue = "", Group = "Main")]
    public string InputInstancePrefix { get; set; }
    
    [Parameter("TPO Timeframe: timeframe for TPO data. Equal or higher than chart's. Set to chart TF or lower for normal mode.", DefaultValue = "Minute")]
    public TimeFrame InputTPOTimeframe { get; set; }
    
    #endregion
    
    #region ColorsAndLooks
    
    [Parameter("Start Color", DefaultValue = "#990000FF", Group = "Colors and Looks")]
    public Color InputStartColor { get; set; }
    
    [Parameter("End Color", DefaultValue = "#99FF0000", Group = "Colors and Looks")]
    public Color InputEndColor { get; set; }

    [Parameter("ColorBullBear: If true, colors are from bars' direction.", DefaultValue = false, Group = "Colors and Looks")]
    public bool InputColorBullBear { get; set; }
    
    [Parameter("MedianColor", DefaultValue = "White", Group = "Colors and Looks")]
    public Color InputMedianColor { get; set; }
    
    [Parameter("Value Area Sides Color", DefaultValue = "White", Group = "Colors and Looks")]
    public Color InputValueAreaSidesColor { get; set; }
    
    [Parameter("Value Area High Low Color", DefaultValue = "White", Group = "Colors and Looks")]
    public Color InputValueAreaHighLowColor { get; set; }
    
    [Parameter("Median Style", DefaultValue = LineStyle.Solid, Group = "Colors and Looks")]
    public LineStyle InputMedianStyle { get; set; }
    
    [Parameter("Median Ray Style", DefaultValue = LineStyle.Lines, Group = "Colors and Looks")]
    public LineStyle InputMedianRayStyle { get; set; }
    
    [Parameter("Value Area Sides Style", DefaultValue = LineStyle.Solid, Group = "Colors and Looks")]
    public LineStyle InputValueAreaSidesStyle { get; set; }
    
    [Parameter("Value Area High Low Style", DefaultValue = LineStyle.Solid, Group = "Colors and Looks")]
    public LineStyle InputValueAreaHighLowStyle { get; set; }
    
    [Parameter("Value Area Ray High Low Style", DefaultValue = LineStyle.Dots, Group = "Colors and Looks")]
    public LineStyle InputValueAreaRayHighLowStyle { get; set; }
    
    [Parameter("Median Width", DefaultValue = 1, Group = "Colors and Looks")]
    public int InputMedianWidth { get; set; }
    
    [Parameter("Median Ray Width", DefaultValue = 1, Group = "Colors and Looks")]
    public int InputMedianRayWidth { get; set; }
    
    [Parameter("Value Area Sides Width", DefaultValue = 1, Group = "Colors and Looks")]
    public int InputValueAreaSidesWidth { get; set; }
    
    [Parameter("Value Area High Low Width", DefaultValue = 1, Group = "Colors and Looks")]
    public int InputValueAreaHighLowWidth { get; set; }
    
    [Parameter("Value Area Ray High Low Width", DefaultValue = 1, Group = "Colors and Looks")]
    public int InputValueAreaRayHighLowWidth { get; set; }
    
    [Parameter("Show Value Area Rays: draw previous value area high/low rays.", DefaultValue = SessionsToDrawRays.None, Group = "Colors and Looks")]
    public SessionsToDrawRays InputShowValueAreaRays { get; set; }
    
    [Parameter("Show Median Rays: draw previous median rays.", DefaultValue = SessionsToDrawRays.None, Group = "Colors and Looks")]
    public SessionsToDrawRays InputShowMedianRays { get; set; }
    
    [Parameter("Rays Until Intersection: which rays stop when hit another MP.", DefaultValue = WaysToStopRays.StopNoRays, Group = "Colors and Looks")]
    public WaysToStopRays InputRaysUntilIntersection { get; set; }
    
    [Parameter("Hide Rays From Invisible Sessions: hide rays from behind the screen.", DefaultValue = false, Group = "Colors and Looks")]
    public bool InputHideRaysFromInvisibleSessions { get; set; }
    
    [Parameter("Time Shift Minutes: shift session + to the left, - to the right.", DefaultValue = 0, Group = "Colors and Looks")]
    public int InputTimeShiftMinutes { get; set; }
    
    [Parameter("Show Key Values: print out VAH, VAL, POC on chart.", DefaultValue = true, Group = "Colors and Looks")]
    public bool InputShowKeyValues { get; set; }
    
    [Parameter("Key Values Color: color for VAH, VAL, POC printout.", DefaultValue = "White", Group = "Colors and Looks")]
    public Color InputKeyValuesColor { get; set; }
    
    [Parameter("Key Values Size: font size for VAH, VAL, POC printout.", DefaultValue = 12, Group = "Colors and Looks")]
    public int InputKeyValuesSize { get; set; }
    
    [Parameter("Show Single Print: mark Single Print profile levels.", DefaultValue = SinglePrintType.No, Group = "Colors and Looks")]
    public SinglePrintType InputShowSinglePrint { get; set; }
    
    [Parameter("Single Print Color", DefaultValue = "Gold", Group = "Colors and Looks")]
    public Color InputSinglePrintColor { get; set; }
    
    [Parameter("Single Print Rays: mark Single Print edges with rays.", DefaultValue = false, Group = "Colors and Looks")]
    public bool InputSinglePrintRays { get; set; }
    
    [Parameter("Single Print Ray Style", DefaultValue = LineStyle.Solid, Group = "Colors and Looks")]
    public LineStyle InputSinglePrintRayStyle { get; set; }
    
    [Parameter("Single Print Ray Width", DefaultValue = 1, Group = "Colors and Looks")]
    public int InputSinglePrintRayWidth { get; set; }
    
    [Parameter("SP Rays Until Intersection: which Single Print rays stop when hit another MP.", DefaultValue = WaysToStopRays.StopNoRays, Group = "Colors and Looks")]
    public WaysToStopRays InputSPRaysUntilIntersection { get; set; }
    
    [Parameter("Prominent Median Color", DefaultValue = "Yellow", Group = "Colors and Looks")]
    public Color InputProminentMedianColor { get; set; }
    
    [Parameter("Prominent Median Style", DefaultValue = LineStyle.Solid, Group = "Colors and Looks")]
    public LineStyle InputProminentMedianStyle { get; set; }
    
    [Parameter("Prominent Median Width", DefaultValue = 4, Group = "Colors and Looks")]
    public int InputProminentMedianWidth { get; set; }
    
    [Parameter("Show TPO Counts", DefaultValue = false, Group = "Colors and Looks")]
    public bool InputShowTpoCounts { get; set; }
    
    [Parameter("TPO Count Above Color", DefaultValue = "Honeydew", Group = "Colors and Looks")]
    public Color InputTpoCountAboveColor { get; set; }
    
    [Parameter("TPO Count Below Color", DefaultValue = "MistyRose", Group = "Colors and Looks")]
    public Color InputTpoCountBelowColor { get; set; }
    
    [Parameter("Right To Left: Draw histogram from right to left.", DefaultValue = false, Group = "Colors and Looks")]
    public bool InputRightToLeft { get; set; }
    
    #endregion
    
    #region Performance
    
    [Parameter("Point Multiplier: higher value = fewer objects. 0 - adaptive.", DefaultValue = 0, MinValue = 0, Step = 10, Group = "Performance")]
    public int InputPointMultiplier { get; set; }
    
    [Parameter("Draw Only Histogram Border", DefaultValue = false, Group = "Performance")]
    public bool InputDrawOnlyHistogramBorder { get; set; }
    
    [Parameter("Disable Histogram: do not draw profile, VAH, VAL, and POC still visible.", DefaultValue = false, Group = "Performance")]
    public bool InputDisableHistogram { get; set; }
    
    [Parameter("Enable Old Profiles Cleanup: delete old profiles exceeding SessionsToCount.", DefaultValue = false, Group = "Performance")]
    public bool InputEnableOldProfilesCleanup { get; set; }
    
    #endregion
    
    #region Alerts
    
    [Parameter("Alert Native: issue native pop-up alerts.", DefaultValue = false, Group = "Alerts")]
    public bool InputAlertNative { get; set; }
    
    [Parameter("Alert Native: sound type.", DefaultValue = SoundType.Announcement, Group = "Alerts")]
    public SoundType InputAlertSoundType { get; set; }
    
    [Parameter("Alert Email: issue email alerts.", DefaultValue = false, Group = "Alerts")]
    public bool InputAlertEmail { get; set; }
    
    [Parameter("Alert Email: Email From.", DefaultValue = "", Group = "Alerts")]
    public string InputAlertEmailFrom { get; set; }
    
    [Parameter("Alert Email: Email To.", DefaultValue = "", Group = "Alerts")]
    public string InputAlertEmailTo { get; set; }
    
    [Parameter("Alert Arrows: draw chart arrows on alerts.", DefaultValue = false, Group = "Alerts")]
    public bool InputAlertArrows { get; set; }
    
    [Parameter("Alert Check Bar: which bar to check for alerts?", DefaultValue = AlertCheckBar.CheckPreviousBar, Group = "Alerts")]
    public AlertCheckBar InputAlertCheckBar { get; set; }
    
    [Parameter("Alert For Value Area: alerts for Value Area (VAH, VAL) rays.", DefaultValue = false, Group = "Alerts")]
    public bool InputAlertForValueArea { get; set; }
    
    [Parameter("Alert For Median: alerts for POC (Median) rays' crossing.", DefaultValue = false, Group = "Alerts")]
    public bool InputAlertForMedian { get; set; }
    
    [Parameter("Alert For Single Print: alerts for single print rays' crossing.", DefaultValue = false, Group = "Alerts")]
    public bool InputAlertForSinglePrint { get; set; }
    
    [Parameter("Alert On Price Break: price breaking above/below the ray.", DefaultValue = false, Group = "Alerts")]
    public bool InputAlertOnPriceBreak { get; set; }
    
    [Parameter("Alert On Candle Close: candle closing above/below the ray.", DefaultValue = false, Group = "Alerts")]
    public bool InputAlertOnCandleClose { get; set; }
    
    [Parameter("Alert On Gap Cross: bar gap above/below the ray.", DefaultValue = false, Group = "Alerts")]
    public bool InputAlertOnGapCross { get; set; }
    
    [Parameter("Alert Arrow Color PB: arrow color for price break alerts.", DefaultValue = "Red", Group = "Alerts")]
    public Color InputAlertArrowColorPb { get; set; }
    
    [Parameter("Alert Arrow Color CC: arrow color for candle close alerts.", DefaultValue = "Blue", Group = "Alerts")]
    public Color InputAlertArrowColorCc { get; set; }
    
    [Parameter("Alert Arrow Color GC: arrow color for gap crossover alerts.", DefaultValue = "Yellow", Group = "Alerts")]
    public Color InputAlertArrowColorGc { get; set; }
    
    #endregion
    
    #region IntradaySettings
    
    #region IntradaySession1
    
    [Parameter("Enable", DefaultValue = true, Group = "Intraday Session 1")]
    public bool InputEnableIntradaySession1 { get; set; }
    
    [Parameter("Start Time", DefaultValue = "00:00", Group = "Intraday Session 1")]
    public string InputIntradaySession1StartTime { get; set; }
    
    [Parameter("End Time", DefaultValue = "06:00", Group = "Intraday Session 1")]
    public string InputIntradaySession1EndTime { get; set; }
    
    [Parameter("Color Start", DefaultValue = "#990000FF", Group = "Intraday Session 1")]
    public Color InputIntradaySession1ColorStart { get; set; }
    
    [Parameter("Color End", DefaultValue = "#99FF0000", Group = "Intraday Session 1")]
    public Color InputIntradaySession1ColorEnd { get; set; }
    
    #endregion
    
    #region IntradaySession2
    
    [Parameter("Enable ", DefaultValue = true, Group = "Intraday Session 2")]
    public bool InputEnableIntradaySession2 { get; set; }
    
    [Parameter("Start Time", DefaultValue = "06:00", Group = "Intraday Session 2")]
    public string InputIntradaySession2StartTime { get; set; }
    
    [Parameter("End Time", DefaultValue = "12:00", Group = "Intraday Session 2")]
    public string InputIntradaySession2EndTime { get; set; }
    
    [Parameter("Color Start", DefaultValue = "#99FF0000", Group = "Intraday Session 2")]
    public Color InputIntradaySession2ColorStart { get; set; }
    
    [Parameter("Color End", DefaultValue = "#9900FF00", Group = "Intraday Session 2")]
    public Color InputIntradaySession2ColorEnd { get; set; }
    
    #endregion
    
    #region IntradaySession3
    
    [Parameter("Enable ", DefaultValue = true, Group = "Intraday Session 3")]
    public bool InputEnableIntradaySession3 { get; set; }
    
    [Parameter("Start Time", DefaultValue = "12:00", Group = "Intraday Session 3")]
    public string InputIntradaySession3StartTime { get; set; }
    
    [Parameter("End Time", DefaultValue = "18:00", Group = "Intraday Session 3")]
    public string InputIntradaySession3EndTime { get; set; }
    
    [Parameter("Color Start", DefaultValue = "#9900FF00", Group = "Intraday Session 3")]
    public Color InputIntradaySession3ColorStart { get; set; }
    
    [Parameter("Color End", DefaultValue = "#990000FF", Group = "Intraday Session 3")]
    public Color InputIntradaySession3ColorEnd { get; set; }
    
    #endregion
    
    #region IntradaySession4
    
    [Parameter("Enable", DefaultValue = true, Group = "Intraday Session 4")]
    public bool InputEnableIntradaySession4 { get; set; }
    
    [Parameter("Start Time", DefaultValue = "18:00", Group = "Intraday Session 4")]
    public string InputIntradaySession4StartTime { get; set; }
    
    [Parameter("End Time", DefaultValue = "00:00", Group = "Intraday Session 4")]
    public string InputIntradaySession4EndTime { get; set; }
    
    [Parameter("Color Start", DefaultValue = "#99FFFF00", Group = "Intraday Session 4")]
    public Color InputIntradaySession4ColorStart { get; set; }
    
    [Parameter("Color End", DefaultValue = "#9900FFFF", Group = "Intraday Session 4")]
    public Color InputIntradaySession4ColorEnd { get; set; }
    
    #endregion
    
    #endregion
    
    #region Miscellaneous
    
    /// <summary>
    /// This seems to only work for the sessions Daily, Weekly and Intraday
    /// </summary>
    [Parameter("Saturday Sunday", DefaultValue = SatSunSolution.SaturdaySundayNormalDays, Group = "Miscellaneous")]
    public SatSunSolution InputSaturdaySunday { get; set; }
    
    [Parameter("Disable alerts on wrong timeframes.", DefaultValue = false, Group = "Miscellaneous")]
    public bool InputDisableAlertsOnWrongTimeframes { get; set; }
    
    [Parameter("Percentage of Median TPOs out of total for a Prominent one.", DefaultValue = 101, Group = "Miscellaneous")]
    public int InputProminentMedianPercentage { get; set; }
    
    [Parameter("Debug - Show Exceptions.", DefaultValue = false, Group = "Miscellaneous")]
    public bool InputShowExceptions { get; set; }
    
    #endregion
    
    #region Hotkeys
    
    [Parameter("Daily", DefaultValue = "Ctrl+1", Group = "Hotkeys")] 
    public string InputHotkeyDaily { get; set; }
    
    [Parameter("Weekly", DefaultValue = "Ctrl+2", Group = "Hotkeys")]
    public string InputHotkeyWeekly { get; set; }
    
    [Parameter("Monthly", DefaultValue = "Ctrl+3", Group = "Hotkeys")]
    public string InputHotkeyMonthly { get; set; }
    
    [Parameter("Quarterly", DefaultValue = "Ctrl+4", Group = "Hotkeys")]
    public string InputHotkeyQuarterly { get; set; }
    
    [Parameter("Semiannual", DefaultValue = "Ctrl+5", Group = "Hotkeys")]
    public string InputHotkeySemiannual { get; set; }
    
    [Parameter("Annual", DefaultValue = "Ctrl+6", Group = "Hotkeys")]
    public string InputHotkeyAnnual { get; set; }
    
    [Parameter("Intraday", DefaultValue = "Ctrl+7", Group = "Hotkeys")]
    public string InputHotkeyIntraday { get; set; }
    
    [Parameter("Rectangle", DefaultValue = "Ctrl+8", Group = "Hotkeys")]
    public string InputHotkeyRectangle { get; set; }
    
    #endregion
    
    #endregion
    
    #region Outputs
    
    [Output("Developing POC", LineColor = "Green", PlotType = PlotType.Points, Thickness = 4)]
    public IndicatorDataSeries OutputDevelopingPoC { get; set; }   
    
    [Output("Developing VAH", LineColor = "Goldenrod", PlotType = PlotType.Points, Thickness = 4)]
    public IndicatorDataSeries OutputDevelopingVah { get; set; }
    
    [Output("Developing VAL 1", LineColor = "Salmon", PlotType = PlotType.Points, Thickness = 4)]
    public IndicatorDataSeries OutputDevelopingVaL { get; set; }
    
    #endregion

    private double _numberOfSlices;
    private int _digitsM;
    private int _tpoSize = 1;
    private TimeFrame _tpoTimeFrame;
    private Bars _htfBars;

    public string ObjPrefix { get; private set; }
    public string StoragePrefix { get; private set; }
    public int TpoSize => _tpoSize;
    public TimeFrame ResolvedTPOTimeframe => _tpoTimeFrame;
    public double OneTickSize { get; private set; }
    public double ValueAreaPercentage { get; private set; }
    public List<IntradaySessionDefinition> IntradaySessions { get; private set; }
    public List<MarketProfileSession> Sessions { get; } = new();
    public MarketProfileCalculator Calculator { get; private set; }
    public MarketProfileRenderer Renderer { get; private set; }
    public HideRaysFromInvisibleSessionsFeature HideRaysFromInvisibleSessionsFeature { get; private set; }
    public SessionState SessionState { get;  set; }
    public SessionChangeManager SessionChangeManager { get; private set; }
    public HotkeyStateManager HotkeyStateManager { get; private set; }
    public SeamlessScrollingManager SeamlessScrollingManager { get; set; }
    public AlertManager AlertManager { get; private set; }
    public NewHighLowManager NewHighLowManager { get; set; }
    public RectangleModeProcessor RectangleModeProcessor { get; private set; }
    
    public event EventHandler<CalculateEventArgs> CalculateEvent;
    private int _lastBarIndex;
    private bool _canDraw;

    protected override void Initialize()
    { 
        //System.Diagnostics.Debugger.Launch();
        
        ObjPrefix = InputInstancePrefix + "_";
        StoragePrefix = string.IsNullOrEmpty(InputInstancePrefix) ? "" : InputInstancePrefix + " ";
        InitializeOneTickSize();
        ValueAreaPercentage = InputValueAreaPercentage / 100.0;
        
        // Resolve TPO timeframe: use chart's if selected is lower.
        var chartSpan = Helpers.GetBarTimeSpan(TimeFrame);
        var tpoSpan = Helpers.GetBarTimeSpan(InputTPOTimeframe);
        _tpoSize = (int)(tpoSpan.TotalSeconds / chartSpan.TotalSeconds);
        if (_tpoSize < 1) _tpoSize = 1;
        _tpoTimeFrame = _tpoSize > 1 ? InputTPOTimeframe : TimeFrame;
        
        // Load HTF bars if needed.
        if (_tpoSize > 1)
            _htfBars = MarketData.GetBars(_tpoTimeFrame);
        
        SessionChangeManager = new SessionChangeManager(this);
        SessionChangeManager.LoadSessionState();
        _canDraw = SessionChangeManager.CheckSessions(SessionState.LastSessionState);
        
        if (!_canDraw)
        {
            Chart.DrawStaticText(ObjPrefix + "MarketProfile-NoSessions", "Please choose a suitable timeframe", VerticalAlignment.Center, HorizontalAlignment.Center, Color.Red);
            return;
        }

        HotkeyStateManager = new HotkeyStateManager(this);
        AlertManager = new AlertManager(this);
        HideRaysFromInvisibleSessionsFeature = new HideRaysFromInvisibleSessionsFeature(this);
        NewHighLowManager = new NewHighLowManager(this);
        
        //ParameterChecks
        CheckIsNotStartFromDateAndSeamlessScrollingMode();
        
        IntradaySessions = GetIntradaySessions();
        
        CheckZeroIntradaySessions();
        
        RemoveAllOwnObjects();
        
        Calculator = new MarketProfileCalculator(this);
        
        // Override the calculator's bar timespan for MTF mode.
        if (_tpoSize > 1)
            Calculator.BarTimeSpan = Helpers.GetBarTimeSpan(_tpoTimeFrame);
        
        Renderer = new MarketProfileRenderer(this, this);
        
        SetSessionsAndAddMarketProfiles(InputSessionsToCount);
        
        RectangleModeProcessor = new RectangleModeProcessor(this);
        
        Bars.BarOpened += _ => AddOrUpdateSessions();
        NewHighLowManager.NewHighOnLastBar += (_, _) => AddOrUpdateSessions();
        NewHighLowManager.NewLowOnLastBar += (_, _) => AddOrUpdateSessions();
        
        HotkeyStateManager.AddHotkeys();
        
        SeamlessScrollingManager = new SeamlessScrollingManager(this);
        
        if (InputSeamlessScrollingMode)
            SeamlessScrollingManager.Start();
        
        if (InputHideRaysFromInvisibleSessions) 
            HideRaysFromInvisibleSessionsFeature.Start();
    }

    public override void Calculate(int index)
    {
        CalculateEvent?.Invoke(this, new CalculateEventArgs { IsLastBar = IsLastBar, Index = index, IsNewBar = index > _lastBarIndex });

        if (index > _lastBarIndex) 
            _lastBarIndex = index;
        
        if (IsLastBar)
            AlertManager?.CheckAlerts(index);
    }

    protected override void OnException(Exception exception)
    {
        if (InputShowExceptions)
        {
            var sb = new StringBuilder();
        
            sb.AppendLine($"Exception: {exception.Message}");
            sb.AppendLine($"StackTrace: {exception.StackTrace}");

            Chart.DrawStaticText(
                $"{ObjPrefix}MarketProfile-Exception", 
                sb.ToString(), 
                VerticalAlignment.Top, 
                HorizontalAlignment.Right, 
                Color.Red);   
        }
        
        Print($"Exception: {exception.Message}");
        Print($"StackTrace: {exception.StackTrace}");
    }

    private void AddOrUpdateSessions()
    {
        //must update last volume profile
        if (SessionState.LastSessionState == SessionPeriod.Rectangle)
            return;

        if (Bars.OpenTimes[Chart.LastVisibleBarIndex] != Bars.Last().OpenTime)
            return;

        var lastSession = Sessions.LastOrDefault();

        if (lastSession == null)
        {
            SetSessionsAndAddMarketProfiles(1);
            return;
        }

        var parsed = DateTime.TryParse(InputStartFromDate, out var startFrom);

        if (parsed && !InputStartFromCurrentSession)
            return;

        // Check if new bar belongs to current session or requires new session
        var needNewSession = IsNewSessionRequired(lastSession, Bars.LastBar.OpenTime);
    
        if (needNewSession)
        {
            SetSessionsAndAddMarketProfiles(1);
            
            // Clean up old profiles if their number exceeds the given SessionsToCount.
            if (InputEnableOldProfilesCleanup && Sessions.Count > InputSessionsToCount)
            {
                var excess = Sessions.Count - InputSessionsToCount;
                for (var i = 0; i < excess; i++)
                {
                    Renderer.DeleteAllFromSession(Sessions[0]);
                    Sessions.RemoveAt(0);
                }
            }
        }
        else
            UpdateLastSession(session: lastSession);
    }

    private void RemoveAllOwnObjects()
    {
        if (string.IsNullOrEmpty(ObjPrefix))
        {
            Chart.RemoveAllObjects();
            return;
        }
        
        var toRemove = new List<string>();
        foreach (var obj in Chart.Objects)
        {
            if (obj.Name.StartsWith(ObjPrefix))
                toRemove.Add(obj.Name);
        }
        foreach (var name in toRemove)
            Chart.RemoveObject(name);
    }

    #region MTF Helpers

    /// <summary>
    /// Gets higher-timeframe bars covering the given session time range.
    /// Uses GetIndexByTime to find the HTF bar that contains the session start time,
    /// mirroring the MT5 iBarShift approach.
    /// </summary>
    private Bar[] GetHTFBarsForRange(DateTime start, DateTime end)
    {
        if (_htfBars == null || _tpoSize <= 1) return null;
        
        var htfStartIdx = _htfBars.OpenTimes.GetIndexByTime(start);
        var htfEndIdx = _htfBars.OpenTimes.GetIndexByTime(end);
        
        if (htfStartIdx < 0) htfStartIdx = 0;
        if (htfEndIdx < 0) return null;
        if (htfStartIdx > htfEndIdx) return null;
        
        var result = new List<Bar>();
        for (var i = htfStartIdx; i <= htfEndIdx; i++)
            result.Add(_htfBars[i]);
        
        return result.Count > 0 ? result.ToArray() : null;
    }

    /// <summary>
    /// In MTF mode, the developing POC/VAH/VAL output buffers only have values at HTF bar boundaries.
    /// This fills the gaps between them so intermediate chart bars display the same value.
    /// </summary>
    private void ForwardFillDevelopingValues(DateTime sessionStart, DateTime sessionEnd)
    {
        var startIdx = Bars.OpenTimes.GetIndexByTime(sessionStart);
        var endIdx = Math.Min(Bars.OpenTimes.GetIndexByTime(sessionEnd), Bars.Count - 1);
        
        // Fill NaN gaps between HTF bar anchor values written by RenderDeveloping*.
        // ClearOutputsOnRange was called first, so only anchor positions have values — gaps are NaN.
        for (var i = startIdx + 1; i <= endIdx; i++)
        {
            if (InputEnableDevelopingPoC && double.IsNaN(OutputDevelopingPoC[i]) && !double.IsNaN(OutputDevelopingPoC[i - 1]))
                OutputDevelopingPoC[i] = OutputDevelopingPoC[i - 1];
            
            if (InputEnableDevelopingValueAtHighValueAtLow)
            {
                if (double.IsNaN(OutputDevelopingVah[i]) && !double.IsNaN(OutputDevelopingVah[i - 1]))
                    OutputDevelopingVah[i] = OutputDevelopingVah[i - 1];
                if (double.IsNaN(OutputDevelopingVaL[i]) && !double.IsNaN(OutputDevelopingVaL[i - 1]))
                    OutputDevelopingVaL[i] = OutputDevelopingVaL[i - 1];
            }
        }
    }

    #endregion

    private void UpdateLastSession(MarketProfileSession session)
    {
        var strategy = SessionState.LastSessionState switch
        {
            SessionPeriod.Daily => SessionProfileStrategyFactory.CreateDaily(this, this),
            SessionPeriod.Weekly => SessionProfileStrategyFactory.CreateWeekly(this, this),
            SessionPeriod.Monthly => SessionProfileStrategyFactory.CreateMonthly(this, this),
            SessionPeriod.Quarterly => SessionProfileStrategyFactory.CreateQuarterly(this, this),
            SessionPeriod.Semiannual => SessionProfileStrategyFactory.CreateSemiannual(this, this),
            SessionPeriod.Annual => SessionProfileStrategyFactory.CreateAnnual(this, this),
            SessionPeriod.Intraday => SessionProfileStrategyFactory.CreateIntradaySessions(IntradaySessions, this, this),
            SessionPeriod.Rectangle => null,
            _ => null
        };

        if (strategy == null) 
            return;
        
        var ranges = strategy.GetSessionRanges(Bars, 1, InputStartColor, InputEndColor).ToArray();
        
        //there should be only one range
        if (ranges.Length != 1)
            throw new Exception("There should be only one range");

        if (session.Range.Start != ranges[0].Start)
            return;

        session.Range = ranges[0];
        
        if (!session.Range.Bars.Any())
        {
            // Skip this session or handle empty range
            Print($"Warning: No bars found for session range {session.Range.Start} to {session.Range.End}");
            return; // or continue to next session
        }
        
        // In MTF mode, use higher-timeframe bars for the calculation.
        var bars = _tpoSize > 1 ? GetHTFBarsForRange(session.Range.Start, session.Range.End) : session.Range.Bars.ToArray();
        
        if (bars == null || bars.Length == 0)
        {
            Print($"Warning: No bars available for session range {session.Range.Start} to {session.Range.End}");
            return;
        }
        var highMinusLow = bars.Max(x => x.High) - bars.Min(x => x.Low);
        var slices = (int) (highMinusLow / OneTickSize);

        if (slices == 0)
        {
            Print($"Slices is 0 | This can't be processed Bars is {bars.Length} (High {bars.Max(x => x.High)} | Low {bars.Min(x => x.Low)} Minus Low is {highMinusLow}) | OneTickSize is {OneTickSize}");
            return;
        }
        
        var profileModel = Calculator.FillAndPileMatrixCalculation(bars, slices, session.Range.StartColor, session.Range.EndColor);
        
        //these two below could be totally removed and reference the latest "developing" values
        var (vah, val) = Calculator.GetValueArea(profileModel.Matrix, InputValueAreaPercentage / 100.0);
        var pointOfControlRowIndex = MarketProfileCalculator.GetPointOfControlRowIndex(profileModel.Matrix);

        if (profileModel.Matrix[pointOfControlRowIndex, 0] == null)
        {
            Print($"Unable to calculate POC because the row is null");
            return;
        }
        
        var pointOfControlPrice = MarketProfileCalculator.GetPointOfControlPrice(profileModel.Matrix, pointOfControlRowIndex);
        
        var (totalTopBlocksAbovePointOfControl, totalBottomBlocksBelowPointOfControl) = MarketProfileCalculator.GetValuesAroundPointOfControl(profileModel.Matrix);
            
        profileModel.ValueAreaHigh = vah;
        profileModel.ValueAreaLow = val;
        profileModel.PointOfControl = pointOfControlPrice;
        profileModel.Median = Calculator.GetMedianPrice(profileModel.Matrix);
        profileModel.TpoCountAbove = totalTopBlocksAbovePointOfControl;
        profileModel.TpoCountBelow = totalBottomBlocksBelowPointOfControl;
        profileModel.SinglePrints.AddRange(MarketProfileCalculator.GetSinglePrints(profileModel.Matrix));
        profileModel.IsProminentLine = Calculator.IsProminentLine(profileModel.Matrix, InputProminentMedianPercentage / 100.0);

        session.Model = profileModel;

        // Clear output buffers for this session range, then re-render developing values.
        // Must happen BEFORE DeleteAllFromSession which empties the developing dictionaries.
        Renderer.ClearOutputsOnRange(session.Range.Start, session.Range.End);
        
        if (InputEnableDevelopingPoC)
            Renderer.RenderDevelopingPoc(profileModel.DevelopingPoC);
            
        if (InputEnableDevelopingValueAtHighValueAtLow)
        {
            Renderer.RenderDevelopingVahs(profileModel.DevelopingAreaHigh);
            Renderer.RenderDevelopingVals(profileModel.DevelopingAreaLow);   
        }
        
        // In MTF mode, forward-fill developing values across intermediate chart bars.
        if (_tpoSize > 1 && (InputEnableDevelopingPoC || InputEnableDevelopingValueAtHighValueAtLow))
            ForwardFillDevelopingValues(session.Range.Start, session.Range.End);

        Renderer.DeleteAllFromSession(session);

        if (InputRightToLeft)
        {
            if (!InputDisableHistogram)
                session.Profile = Renderer.RenderHorizontallyFlippedProfile(profileModel.Matrix, session);
            
            session.ValueArea = Renderer.RenderHorizontallyFlippedValueArea(profileModel, session);
            session.KeyValues = Renderer.RenderHorizontallyFlippedKeyValues(profileModel, session);
            session.TpoCounts = Renderer.RenderHorizontallyFlippedTpoCounts(profileModel, session);
            session.ProminentLine = Renderer.RenderHorizontallyFlippedProminentLine(profileModel, session);
        }
        else
        {
            if (!InputDisableHistogram)
                session.Profile = Renderer.RenderProfile(profileModel.Matrix);
            
            session.ValueArea = Renderer.RenderValueArea(profileModel);
            session.ValueAreaRays = Renderer.RenderValueAreaRays(profileModel, Sessions.Count - 1);
            session.MedianRays = Renderer.RenderPointOfControlRays(profileModel, Sessions.Count - 1);
            session.KeyValues = Renderer.RenderKeyValues(profileModel);
            session.TpoCounts = Renderer.RenderTpoCounts(profileModel);
            session.SinglePrints = Renderer.RenderSinglePrints(profileModel);
            session.ProminentLine = Renderer.RenderProminentLine(profileModel);
            
            Renderer.PostProcessPointOfControlRays(Sessions);
            Renderer.PostProcessValueAreaRays(Sessions);
            Renderer.PostProcessSinglePrintRays(Sessions);
         }
    }

    public bool IsNewSessionRequired(MarketProfileSession lastSession, DateTime newBarTime)
    {
        // If new bar's time is after the session's end time, we need a new session
        if (newBarTime > lastSession.Range.End)
            return true;

        return SessionState.LastSessionState switch
        {
            SessionPeriod.Daily =>
                // For daily, check if we've crossed into a new day (adjusted for market open)
                !SameSessionDay(lastSession.Range.Start, newBarTime),
            SessionPeriod.Weekly =>
                // For weekly, check if we've crossed into a new week
                GetWeekNumber(lastSession.Range.Start) != GetWeekNumber(newBarTime),
            SessionPeriod.Monthly =>
                // For monthly, check if we've crossed into a new month
                lastSession.Range.Start.Month != newBarTime.Month || lastSession.Range.Start.Year != newBarTime.Year,
            SessionPeriod.Quarterly =>
                // For quarterly, check if we've crossed into a new quarter
                GetQuarter(lastSession.Range.Start) != GetQuarter(newBarTime) || lastSession.Range.Start.Year != newBarTime.Year,
            SessionPeriod.Semiannual =>
                // For semiannual, check if we've crossed into a new half-year
                (lastSession.Range.Start.Month <= 6 && newBarTime.Month > 6) || (lastSession.Range.Start.Month > 6 && newBarTime.Month <= 6) || lastSession.Range.Start.Year != newBarTime.Year,
            SessionPeriod.Annual =>
                // For annual, check if we've crossed into a new year
                lastSession.Range.Start.Year != newBarTime.Year,
            SessionPeriod.Intraday =>
                // For intraday, check if the new bar is in a different intraday session
                !SameIntradaySession(lastSession.Range.Start, newBarTime),
            _ => false
        };
    }

    private bool SameIntradaySession(DateTime sessionStart, DateTime barTime)
    {
        // Implement logic to check if a bar belongs to the same intraday session
        // This would need to check the intraday session definitions
        foreach (var session in IntradaySessions)
        {
            // Check if both times fall within the same intraday session
            // This is a simplified check and may need to be enhanced
            if (IsInIntradaySession(sessionStart, session) && 
                IsInIntradaySession(barTime, session))
                return true;
        }
        return false;
    }
    
    private void InitializeOneTickSize()
    {
        if (InputPointMultiplier == 0)
        {
            var quote = Ask;
            
            //Get chart dimensions and price range
            var chartHeight = Chart.Height;
            var chartPriceMax = Chart.TopY;
            var chartPriceMin = Chart.BottomY;
            var priceDiff = chartPriceMax - chartPriceMin;
            
            //todo Chart.BottomY is -0 when backtesting, should report for fixing
            //Print($"Chart Price Max {chartPriceMax} Min {chartPriceMin} Diff {priceDiff}");

            if ((chartHeight <= 0) || (priceDiff <= 0)) //If no chart yet, do it old fashion
            {
                var s = quote.ToString($"F{Symbol.Digits}");
                var totalDigits = s.Length;

                // If there is a dot in a quote.
                if (s.Contains(',') || s.Contains('.'))
                    totalDigits--; // Decrease the count of digits by one.

                _numberOfSlices = totalDigits <= 5 ? 1 : (int)Math.Pow(10, totalDigits - 5);
            }
            else // otherwise, calculate the multiplier so that 1 TPO = 1 pixel
            {
                var pricePerPixel = priceDiff / chartHeight;
                _numberOfSlices = (int)Math.Round(pricePerPixel / Symbol.TickSize);
            }
        }
        else
        {
            _numberOfSlices = InputPointMultiplier;
        }
        
        // Based on number of digits in PointMultiplier_calculated. -1 because if PointMultiplier_calculated < 10, it does not modify the number of digits.
        _digitsM = Math.Max(0, Symbol.Digits - (_numberOfSlices.ToString(CultureInfo.InvariantCulture).Length - 1));
        OneTickSize = Math.Round(Symbol.TickSize * _numberOfSlices, _digitsM);

        // Adjust for TickSize granularity if needed.
        var tickSize = Symbol.TickSize;
        if (OneTickSize < tickSize)
        {
            _digitsM = Symbol.Digits - (((int)Math.Round(tickSize / Symbol.TickSize)).ToString().Length - 1);
            OneTickSize = Math.Round(tickSize, _digitsM);
        }
    }
    
    public bool SetSessionsAndAddMarketProfiles(int sessionsToCount, DateTime? endAt = null)
    {
        var strategy = SessionState.LastSessionState switch
        {
            SessionPeriod.Daily => SessionProfileStrategyFactory.CreateDaily(this, this),
            SessionPeriod.Weekly => SessionProfileStrategyFactory.CreateWeekly(this, this),
            SessionPeriod.Monthly => SessionProfileStrategyFactory.CreateMonthly(this, this),
            SessionPeriod.Quarterly => SessionProfileStrategyFactory.CreateQuarterly(this, this),
            SessionPeriod.Semiannual => SessionProfileStrategyFactory.CreateSemiannual(this, this),
            SessionPeriod.Annual => SessionProfileStrategyFactory.CreateAnnual(this, this),
            SessionPeriod.Intraday => SessionProfileStrategyFactory.CreateIntradaySessions(IntradaySessions, this, this),
            SessionPeriod.Rectangle => null,
            _ => null
        };

        if (strategy == null) 
            return false;
        
        var ranges = strategy.GetSessionRanges(Bars, sessionsToCount, InputStartColor, InputEndColor, endAt).ToArray();

        for (var index = 0; index < ranges.Length; index++)
        {
            var range = ranges[index];
            
            Renderer.ClearOutputsOnRange(range.Start, range.End);
            
            var session = new MarketProfileSession
            {
                Range = range,
            };

            BuildAndRender(range, session, index, ranges.Length);
        }
        
        Renderer.PostProcessPointOfControlRays(Sessions);
        Renderer.PostProcessValueAreaRays(Sessions);
        Renderer.PostProcessSinglePrintRays(Sessions);
        return true;
    }

    private bool BuildAndRender(SessionRange range, MarketProfileSession session, int index, int total)
    {
        if (!range.Bars.Any())
        {
            // Skip this session or handle empty range
            Print($"Warning: No bars found for session range {range.Start} to {range.End}");
            return false; // or continue to next session
        }
        
        // In MTF mode, use higher-timeframe bars for the calculation.
        var bars = _tpoSize > 1 ? GetHTFBarsForRange(range.Start, range.End) : range.Bars.ToArray();
        
        if (bars == null || bars.Length == 0)
        {
            Print($"Warning: No bars available for session range {range.Start} to {range.End}");
            return false;
        }
        
        var slices = Calculator.GetSlices(bars);

        if (slices == 0)
        {
            Print($"Slices is 0 | This can't be processed Bars is {bars.Length} (High {bars.Max(x => x.High)} | Low {bars.Min(x => x.Low)} | OneTickSize is {OneTickSize}");
            return false;
        }
        
        var profileModel = Calculator.FillAndPileMatrixCalculation(bars, slices, range.StartColor, range.EndColor);
        
        //these two below could be totally removed and reference the latest "developing" values
        //var (vah, val) = Calculator.GetValueArea(profileModel.Matrix, InputValueAreaPercentage / 100.0);
        var vah = profileModel.DevelopingAreaHigh.LastOrDefault().Value;
        var val = profileModel.DevelopingAreaLow.LastOrDefault().Value;
        var pointOfControlRowIndex = MarketProfileCalculator.GetPointOfControlRowIndex(profileModel.Matrix);

        if (profileModel.Matrix[pointOfControlRowIndex, 0] == null)
        {
            Print($"Unable to calculate POC because the row is null");
            return false;
        }
        
        var pointOfControlPrice = MarketProfileCalculator.GetPointOfControlPrice(profileModel.Matrix, pointOfControlRowIndex);
        
        var (totalTopBlocksAbovePointOfControl, totalBottomBlocksBelowPointOfControl) = MarketProfileCalculator.GetValuesAroundPointOfControl(profileModel.Matrix);
            
        profileModel.ValueAreaHigh = vah;
        profileModel.ValueAreaLow = val;
        profileModel.PointOfControl = pointOfControlPrice;
        profileModel.Median = Calculator.GetMedianPrice(profileModel.Matrix);
        profileModel.TpoCountAbove = totalTopBlocksAbovePointOfControl;
        profileModel.TpoCountBelow = totalBottomBlocksBelowPointOfControl;
        profileModel.SinglePrints.AddRange(MarketProfileCalculator.GetSinglePrints(profileModel.Matrix));
        profileModel.IsProminentLine = Calculator.IsProminentLine(profileModel.Matrix, InputProminentMedianPercentage / 100.0);

        session.Model = profileModel;
        
        if (index == 0 && InputRightToLeft)
        {
            if (!InputDisableHistogram)
                session.Profile = Renderer.RenderHorizontallyFlippedProfile(profileModel.Matrix, session);
                
            session.ValueArea = Renderer.RenderHorizontallyFlippedValueArea(profileModel, session);
            session.KeyValues = Renderer.RenderHorizontallyFlippedKeyValues(profileModel, session);
            session.TpoCounts = Renderer.RenderHorizontallyFlippedTpoCounts(profileModel, session);
            session.ProminentLine = Renderer.RenderHorizontallyFlippedProminentLine(profileModel, session);
        }
        else
        {
            if (!InputDisableHistogram)
                session.Profile = Renderer.RenderProfile(profileModel.Matrix);
                
            session.ValueArea = Renderer.RenderValueArea(profileModel);
            session.ValueAreaRays = Renderer.RenderValueAreaRays(profileModel, index);
            session.MedianRays = Renderer.RenderPointOfControlRays(profileModel, index);
            session.KeyValues = Renderer.RenderKeyValues(profileModel);
            session.TpoCounts = Renderer.RenderTpoCounts(profileModel);
            session.ProminentLine = Renderer.RenderProminentLine(profileModel);
        }
        
        if (InputEnableDevelopingPoC)
            Renderer.RenderDevelopingPoc(profileModel.DevelopingPoC);
            
        if (InputEnableDevelopingValueAtHighValueAtLow)
        {
            Renderer.RenderDevelopingVahs(profileModel.DevelopingAreaHigh);
            Renderer.RenderDevelopingVals(profileModel.DevelopingAreaLow);   
        }
        
        // In MTF mode, forward-fill developing values across intermediate chart bars.
        if (_tpoSize > 1 && (InputEnableDevelopingPoC || InputEnableDevelopingValueAtHighValueAtLow))
            ForwardFillDevelopingValues(range.Start, range.End);
        session.SinglePrints = Renderer.RenderSinglePrints(profileModel);
            
        Sessions.Add(session);

        return true;
    }

    public List<IntradaySessionDefinition> GetIntradaySessions()
    {
        //initialize intraday sessions
        var sessions = new List<IntradaySessionDefinition>();
        
        if (InputEnableIntradaySession1)
            sessions.Add(new IntradaySessionDefinition
            {
                Name = "Intraday 1",
                Start = TimeSpan.Parse(InputIntradaySession1StartTime),
                End = TimeSpan.Parse(InputIntradaySession1EndTime),
                StartColor = InputIntradaySession1ColorStart,
                EndColor = InputIntradaySession1ColorEnd
            });
        
        if (InputEnableIntradaySession2)
            sessions.Add(new IntradaySessionDefinition
            {
                Name = "Intraday 2",
                Start = TimeSpan.Parse(InputIntradaySession2StartTime),
                End = TimeSpan.Parse(InputIntradaySession2EndTime),
                StartColor = InputIntradaySession2ColorStart,
                EndColor = InputIntradaySession2ColorEnd
            });
        
        if (InputEnableIntradaySession3)
            sessions.Add(new IntradaySessionDefinition
            {
                Name = "Intraday 3",
                Start = TimeSpan.Parse(InputIntradaySession3StartTime),
                End = TimeSpan.Parse(InputIntradaySession3EndTime),
                StartColor = InputIntradaySession3ColorStart,
                EndColor = InputIntradaySession3ColorEnd
            });
        
        if (InputEnableIntradaySession4)
            sessions.Add(new IntradaySessionDefinition
            {
                Name = "Intraday 4",
                Start = TimeSpan.Parse(InputIntradaySession4StartTime),
                End = TimeSpan.Parse(InputIntradaySession4EndTime),
                StartColor = InputIntradaySession4ColorStart,
                EndColor = InputIntradaySession4ColorEnd
            });

        return sessions;
    }

    #region Checks
    
    private void CheckIsNotStartFromDateAndSeamlessScrollingMode()
    {
        var parsed = DateTime.TryParse(InputStartFromDate, out var startFrom);
        
        if (parsed && InputSeamlessScrollingMode && !InputStartFromCurrentSession)
            throw new ArgumentException("Seamless scrolling mode doesn't work with Start From Date Mode.");
    }
    
    public void CheckZeroIntradaySessions()
    {
        if (IntradaySessions.Count == 0 && SessionState.LastSessionState == SessionPeriod.Intraday)
            throw new ArgumentException("Enable at least one intraday session if you want to use Intraday mode.");
    }
    
    #endregion
}

================================================
FILE: MarketProfile/MarketProfile/MarketProfile.csproj
================================================
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="cTrader.Automate" Version="*" />
  </ItemGroup>
</Project>

================================================
FILE: MarketProfile/MarketProfile/MarketProfileRenderer.cs
================================================
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Internals;

namespace cAlgo;

public interface IMarketProfileRendererSettings
{
    bool InputColorBullBear { get; }
    Color InputStartColor { get; }
    Color InputEndColor { get; }
    Color InputMedianColor { get; }
    Color InputValueAreaSidesColor { get; }
    Color InputValueAreaHighLowColor { get; }
    LineStyle InputMedianStyle { get; }
    LineStyle InputMedianRayStyle { get; }
    LineStyle InputValueAreaSidesStyle { get; }
    LineStyle InputValueAreaHighLowStyle { get; }
    LineStyle InputValueAreaRayHighLowStyle { get; }
    int InputMedianWidth { get; }
    int InputMedianRayWidth { get; }
    int InputValueAreaSidesWidth { get; }
    int InputValueAreaHighLowWidth { get; }
    int InputValueAreaRayHighLowWidth { get; }
    SessionsToDrawRays InputShowValueAreaRays { get; }
    SessionsToDrawRays InputShowMedianRays { get; }
    WaysToStopRays InputRaysUntilIntersection { get; }
    // bool InputHideRaysFromInvisibleSessions { get; }
    int InputTimeShiftMinutes { get; }
    bool InputShowKeyValues { get; }
    Color InputKeyValuesColor { get; }
    int InputKeyValuesSize { get; }
    SinglePrintType InputShowSinglePrint { get; }
    Color InputSinglePrintColor { get; }
    bool InputSinglePrintRays { get; }
    LineStyle InputSinglePrintRayStyle { get; }
    int InputSinglePrintRayWidth { get; }
    WaysToStopRays InputSPRaysUntilIntersection { get; }
    Color InputProminentMedianColor { get; }
    bool InputDrawOnlyHistogramBorder { get; }
    LineStyle InputProminentMedianStyle { get; } 
    int InputProminentMedianWidth { get; }
    bool InputShowTpoCounts { get; }
    Color InputTpoCountAboveColor { get; }
    Color InputTpoCountBelowColor { get; }
    // bool InputRightToLeft { get; }
    MarketProfileCalculator Calculator { get; }
    string ObjPrefix { get; }
    Bars Bars { get; }
    IndicatorDataSeries OutputDevelopingPoC { get; }
    IndicatorDataSeries OutputDevelopingVah { get; }
    IndicatorDataSeries OutputDevelopingVaL { get; }
}

public class MarketProfileRenderer : IMarketProfileResources, IMarketProfileRendererSettings
{
    private readonly IMarketProfileResources _resources;
    private readonly IMarketProfileRendererSettings _settings;
    private readonly string _format;
    private readonly TimeSpan _barTimeSpan;

    public MarketProfileRenderer(IMarketProfileResources resources, IMarketProfileRendererSettings settings)
    {
        _resources = resources;
        _settings = settings;
        _format = $"0.{new string('0', Symbol.Digits)}";
        _barTimeSpan = Helpers.GetBarTimeSpan(TimeFrame);
    }

    public List<ChartObject> RenderProfile(MatrixPoint[,] matrix)
    {
        // var stopwatch = Stopwatch.StartNew();
        
        var result = new List<ChartObject>();
        
        var matrixStartTime = DateTime.MinValue.Ticks;
        
        for (var row = 0; row < matrix.GetLength(0); row++)
        {
            if (matrix[row, 0] == null)
                continue;
            
            matrixStartTime = matrix[row, 0].StartTime.Ticks;
            break;
        }
        
        for (var row = 0; row < matrix.GetLength(0); row++)
        for (var column = 0; column < matrix.GetLength(1); column++)
        {
            var point = matrix[row, column];

            if (point == null)
                continue;

            var nextPoint = matrix[row, Math.Min(column + 1, matrix.GetLength(1) - 1)];

            if (InputDrawOnlyHistogramBorder && nextPoint != null)
                continue;

            //Override the color with the direction if InputColorBullBear is true
            Color color;
            
            if (InputColorBullBear)
                color = point.Direction == Direction.Up ? InputStartColor : InputEndColor;
            else
                color = point.Color;

            var r = Chart.DrawRectangle(
                $"{ObjPrefix}{matrixStartTime}-row{row}column{column}", 
                point.StartTime.AddMinutes(-InputTimeShiftMinutes), 
                point.Bottom, 
                point.EndTime.AddMinutes(-InputTimeShiftMinutes), 
                point.Top, 
                color);
            //r.LineStyle = LineStyle.DotsVeryRare;

            r.Thickness = 0;
            r.IsFilled = true;
            //r.IsInteractive = true;
            
            result.Add(r);
        }
        
        // stopwatch.Stop();
        // Print($"RenderProfile Took: {stopwatch.ElapsedMilliseconds} ms");
        //
        return result;
    }

    public List<ChartObject> RenderHorizontallyFlippedProfile(MatrixPoint[,] matrix, MarketProfileSession session)
    {
        var result = new List<ChartObject>();

        // Find the earliest and latest times in the matrix
        DateTime earliestTime = DateTime.MaxValue;
        DateTime latestTime = DateTime.MinValue;
        
        var startPoint = session.Range.Bars.Last().OpenTime;

        for (var row = 0; row < matrix.GetLength(0); row++)
        for (var column = 0; column < matrix.GetLength(1); column++)
        {
            var point = matrix[row, column];
            if (point == null)
                continue;

            earliestTime = DateTime.Compare(earliestTime, point.StartTime) > 0 ? point.StartTime : earliestTime;
            latestTime = DateTime.Compare(latestTime, point.EndTime) < 0 ? point.EndTime : latestTime;
        }

        var timeOffset = startPoint - latestTime;

        var matrixStartTime = DateTime.MinValue.Ticks;
        
        for (var row = 0; row < matrix.GetLength(0); row++)
        {
            if (matrix[row, 0] == null)
                continue;
            
            matrixStartTime = matrix[row, 0].StartTime.Ticks;
            break;
        }

        // Draw each point with flipped times
        for (var row = 0; row < matrix.GetLength(0); row++)
        {
            for (var column = 0; column < matrix.GetLength(1); column++)
            {
                var point = matrix[row, column];
                if (point == null)
                    continue;
                
                var nextPoint = matrix[row, Math.Min(column + 1, matrix.GetLength(1) - 1)];
                
                if (InputDrawOnlyHistogramBorder && nextPoint != null)
                    continue;

                // Calculate flipped times
                TimeSpan startOffset = point.StartTime - earliestTime;
                TimeSpan endOffset = point.EndTime - earliestTime;
                
                DateTime flippedStartTime = latestTime - endOffset + timeOffset;
                DateTime flippedEndTime = latestTime - startOffset + timeOffset;

                // Set color
                Color color;
                if (InputColorBullBear)
                    color = point.Direction == Direction.Up ? InputStartColor : InputEndColor;
                else
                    color = point.Color;

                var r = Chart.DrawRectangle(
                    $"{ObjPrefix}{matrixStartTime}-{row}{column}",
                    flippedStartTime.AddMinutes(-InputTimeShiftMinutes),
                    point.Bottom,
                    flippedEndTime.AddMinutes(-InputTimeShiftMinutes),
                    point.Top,
                    color);

                r.Thickness = 0;
                r.IsFilled = true;

                result.Add(r);
            }
        }

        return result;
    }
    
    public List<ChartObject> RenderValueArea(MarketProfileModel model)
    {
        var vah = model.ValueAreaHigh;
        var val = model.ValueAreaLow;
        var pointOfControl = model.PointOfControl;
        var pointOfControlRowEndTime = Calculator.GetPointOfControlRowEndTime(model.Matrix);
        
        var matrixStartTime = DateTime.MinValue.Ticks;
        
        for (var row = 0; row < model.Matrix.GetLength(0); row++)
        {
            if (model.Matrix[row, 0] == null)
                continue;
            
            matrixStartTime = model.Matrix[row, 0].StartTime.Ticks;
            break;
        }
        
        //Print($"Median: {median} val: {val} vah: {vah}");
        //Chart.DrawVerticalLine($"Left-{Guid.NewGuid()}", model.EndTime, Color.White);

        var result = new List<ChartObject>
        {
            Chart.DrawTrendLine($"{ObjPrefix}VAH-{matrixStartTime}", model.StartTime, val, pointOfControlRowEndTime, val, InputValueAreaHighLowColor, InputValueAreaHighLowWidth, InputValueAreaHighLowStyle),
            Chart.DrawTrendLine($"{ObjPrefix}VAL-{matrixStartTime}", model.StartTime, vah, pointOfControlRowEndTime, vah, InputValueAreaHighLowColor, InputValueAreaHighLowWidth, InputValueAreaHighLowStyle),
            Chart.DrawTrendLine($"{ObjPrefix}Left-Boundary-{matrixStartTime}", model.StartTime, val, model.StartTime, vah, InputValueAreaSidesColor, InputValueAreaSidesWidth, InputValueAreaSidesStyle),
            Chart.DrawTrendLine($"{ObjPrefix}Right-Boundary-{matrixStartTime}", pointOfControlRowEndTime, val, pointOfControlRowEndTime, vah, InputValueAreaSidesColor, InputValueAreaSidesWidth, InputValueAreaSidesStyle),
            Chart.DrawTrendLine($"{ObjPrefix}PointOfControl-{matrixStartTime}", model.StartTime, pointOfControl, pointOfControlRowEndTime, pointOfControl, InputMedianColor, InputMedianWidth, InputMedianStyle),
        };

        return result;
    }

    public List<ChartObject> RenderHorizontallyFlippedValueArea(MarketProfileModel model, MarketProfileSession session)
    {
        var valueAreaObjects = RenderValueArea(model);
        var pointOfControlRowEndTime = Calculator.GetPointOfControlRowEndTime(model.Matrix);
        var timeSpan = pointOfControlRowEndTime - model.StartTime;

        foreach (var obj in valueAreaObjects)
        {
            if (obj is not ChartTrendLine trendLine)
                continue;

            if (trendLine.Name.Contains("VAH") || trendLine.Name.Contains("VAL"))
            {
                trendLine.Time1 = session.Range.Bars.Last().OpenTime;
                trendLine.Time2 = trendLine.Time1.Add(-timeSpan);
            }
            else if (trendLine.Name.Contains("Left"))
            {
                trendLine.Time1 = session.Range.Bars.Last().OpenTime;
                trendLine.Time2 = session.Range.Bars.Last().OpenTime;
            }
            else if (trendLine.Name.Contains("Right"))
            {
                trendLine.Time1 = session.Range.Bars.Last().OpenTime.Add(-timeSpan);
                trendLine.Time2 = session.Range.Bars.Last().OpenTime.Add(-timeSpan);
            }
            else if (trendLine.Name.Contains("PointOfControl"))
            {
                trendLine.Time1 = session.Range.Bars.Last().OpenTime;
                trendLine.Time2 = trendLine.Time1.Add(-timeSpan);
            }
        }

        return valueAreaObjects;
    }

    public List<ChartObject> RenderPointOfControlRays(MarketProfileModel model, int index)
    {
        var result = new List<ChartObject>();
        var pointOfControlRowEndTime = Calculator.GetPointOfControlRowEndTime(model.Matrix);
        var medianRowEndTime1MinAfter = pointOfControlRowEndTime.AddMinutes(1);
        
        switch (InputShowMedianRays)
        {
            case SessionsToDrawRays.None:
                break;
            case SessionsToDrawRays.Previous when index == 1:
            case SessionsToDrawRays.Current when index == 0:
            case SessionsToDrawRays.PreviousCurrent when index <= 1:
            case SessionsToDrawRays.AllPrevious when index > 0:
            case SessionsToDrawRays.All:
                var pointOfControlRay = Chart.DrawTrendLine($"{ObjPrefix}PointOfControlRay-Previous-{model.StartTime}", pointOfControlRowEndTime, model.PointOfControl, medianRowEndTime1MinAfter, model.PointOfControl, InputMedianColor, InputMedianRayWidth, InputMedianRayStyle);
                pointOfControlRay.ExtendToInfinity = true;
                result.Add(pointOfControlRay);
                break;
        }
        
        return result;
    }

    public void RenderDevelopingVals(Dictionary<DateTime, double> developingVals)
    {
        foreach (var (col, valRow) in developingVals)
        {
            var idx = Bars.OpenTimes.GetIndexByTime(col);
            OutputDevelopingVaL[idx] = valRow;
        }
    }

    public void ClearDevelopingVals(Dictionary<DateTime, double> developingVals)
    {
        foreach (var (col, valRow) in developingVals)
        {
            var idx = Bars.OpenTimes.GetIndexByTime(col);
            OutputDevelopingVaL[idx] = double.NaN;
        }
    }
    
    public void RenderDevelopingVahs(Dictionary<DateTime, double> developingVahs)
    {
        foreach (var (col, vahRow) in developingVahs)
        {
            var idx = Bars.OpenTimes.GetIndexByTime(col);
            OutputDevelopingVah[idx] = vahRow;
        }
    }

    public void ClearDevelopingVahs(Dictionary<DateTime, double> developingVahs)
    {
        foreach (var (col, vahRow) in developingVahs)
        {
            var idx = Bars.OpenTimes.GetIndexByTime(col);
            OutputDevelopingVah[idx] = double.NaN;
        }
    }
    
    public void RenderDevelopingPoc(Dictionary<DateTime, double> developingPoC)
    {
        foreach (var (col, pocRow) in developingPoC)
        {
            var idx = Bars.OpenTimes.GetIndexByTime(col);
            OutputDevelopingPoC[idx] = pocRow;
        }
    }

    public void ClearDevelopingPoc(Dictionary<DateTime, double> developingPoC)
    {
        foreach (var (col, pocRow) in developingPoC)
        {
            var idx = Bars.OpenTimes.GetIndexByTime(col);
            OutputDevelopingPoC[idx] = double.NaN;
        }
    }

    public void PostProcessPointOfControlRays(List<MarketProfileSession> sessions)
    {
        var sortedSessions = sessions.OrderBy(s => s.Model.StartTime).ToList();
        
        for (var i = 0; i < sortedSessions.Count; i++)
        {
            if (sortedSessions[i].MedianRays != null)
                PostProcessPointOfControlRay(sortedSessions[i].MedianRays.FirstOrDefault(), i, sortedSessions);
        }
    }

    public void PostProcessPointOfControlRay(ChartObject pointOfControlRay, int index, List<MarketProfileSession> sessions)
    {
        if (pointOfControlRay is not ChartTrendLine trendLine)
            return;
        
        //let's try only with index = 0
        var session = sessions[index];

        if (index == sessions.Count - 1)
            return;

        for (var i = index + 1; i < sessions.Count; i++)
        {
            var nextSession = sessions[i];
            
            var nextSessionTopPrice = MarketProfileCalculator.GetTopPrice(nextSession.Model.Matrix);
            var nextSessionBottomPrice = MarketProfileCalculator.GetBottomPrice(nextSession.Model.Matrix);

            //For debugging
            // switch (InputRaysUntilIntersection)
            // {
            //     case WaysToStopRays.StopNoRays:
            //         break;
            //     case WaysToStopRays.StopAllRays:
            //         break;
            //     case WaysToStopRays.StopAllRaysExceptPrevSession:
            //         if (index != sessions.Count - 2)
            //             trendLine.Color = Color.HotPink;
            //         break;
            //     case WaysToStopRays.StopOnlyPreviousSession:
            //         if (index == sessions.Count - 2)
            //             trendLine.Color = Color.Lime;
            //         break;
            // }

            if (session.Model.PointOfControl >= nextSessionBottomPrice && session.Model.PointOfControl <= nextSessionTopPrice)
            {
                switch (InputRaysUntilIntersection)
                {
                    case WaysToStopRays.StopNoRays:
                        trendLine.ExtendToInfinity = true;
                        return;
                    case WaysToStopRays.StopAllRays:
                        trendLine.ExtendToInfinity = false;
                        trendLine.Time2 = nextSession.Model.StartTime;
                        return;
                    case WaysToStopRays.StopAllRaysExceptPrevSession:
                        if (index != sessions.Count - 2)
                        {
                            trendLine.ExtendToInfinity = false;
                            trendLine.Time2 = nextSession.Model.StartTime;;
                        }
                        return;
                    case WaysToStopRays.StopOnlyPreviousSession:
                        if (index == sessions.Count - 2)
                        {
                            trendLine.ExtendToInfinity = false;
                            trendLine.Time2 = nextSession.Model.StartTime;
                        }
                        return;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
        }
    }

    public List<ChartObject> RenderValueAreaRays(MarketProfileModel model, int index)
    {
        var result = new List<ChartObject>();
        var pointOfControlRowEndTime = Calculator.GetPointOfControlRowEndTime(model.Matrix);
        //since this is a ray, I just need 1 min after to draw in the right direction
        var pointOfControlRowEndTime1MinAfter = pointOfControlRowEndTime.AddMinutes(1);

        var matrixStartTime = DateTime.MinValue.Ticks;

        for (var row = 0; row < model.Matrix.GetLength(0); row++)
        {
            if (model.Matrix[row, 0] == null)
                continue;
            
            matrixStartTime = model.Matrix[row, 0].StartTime.Ticks;
            break;
        }
        
        switch (InputShowValueAreaRays)
        {
            case SessionsToDrawRays.None:
                break;
            case SessionsToDrawRays.Previous when index == 1:
            case SessionsToDrawRays.Current when index == 0:
            case SessionsToDrawRays.PreviousCurrent when index <= 1:
            case SessionsToDrawRays.AllPrevious when index > 0:
            case SessionsToDrawRays.All:
                var varHigh = Chart.DrawTrendLine($"{ObjPrefix}ValueAreaRayHigh-Previous-{matrixStartTime}", pointOfControlRowEndTime, model.ValueAreaHigh, pointOfControlRowEndTime1MinAfter, model.ValueAreaHigh, InputValueAreaHighLowColor, InputValueAreaRayHighLowWidth, InputValueAreaRayHighLowStyle);
                varHigh.ExtendToInfinity = true;
                result.Add(varHigh);
                    
                var varLow = Chart.DrawTrendLine($"{ObjPrefix}ValueAreaRayLow-Previous-{matrixStartTime}", pointOfControlRowEndTime, model.ValueAreaLow, pointOfControlRowEndTime1MinAfter, model.ValueAreaLow, InputValueAreaHighLowColor, InputValueAreaRayHighLowWidth, InputValueAreaRayHighLowStyle);
                varLow.ExtendToInfinity = true;
                result.Add(varLow);
                break;
        }
        
        return result;
    }

    public void PostProcessValueAreaRays(List<MarketProfileSession> sessions)
    {
        var sortedSessions = sessions.OrderBy(s => s.Model.StartTime).ToList();
        
        for (var i = 0; i < sortedSessions.Count; i++)
        {
            if (sortedSessions[i].ValueAreaRays == null)
                continue;
            
            //PostProcessValueAreaRay(sessions[i].ValueAreaRays, i, sessions);
            PostProcessValueAreaRay(sortedSessions[i].ValueAreaRays.FirstOrDefault(), i, sortedSessions);
            PostProcessValueAreaRay(sortedSessions[i].ValueAreaRays.LastOrDefault(), i, sortedSessions);
        }
    }

    private void PostProcessValueAreaRay(ChartObject valueAreaRay, int index, List<MarketProfileSession> sessions)
    {
        if (valueAreaRay is not ChartTrendLine trendLine)
            return;
        
        var referencePrice = trendLine.Name.Contains("High") ? sessions[index].Model.ValueAreaHigh : sessions[index].Model.ValueAreaLow;
        
        if (index == sessions.Count - 1)
            return;

        for (var i = index + 1; i < sessions.Count; i++)
        {
            var nextSession = sessions[i];
            
            var nextSessionTopPrice = MarketProfileCalculator.GetTopPrice(nextSession.Model.Matrix);
            var nextSessionBottomPrice = MarketProfileCalculator.GetBottomPrice(nextSession.Model.Matrix);

            //For debugging
            // switch (InputRaysUntilIntersection)
            // {
            //     case WaysToStopRays.StopNoRays:
            //         break;
            //     case WaysToStopRays.StopAllRays:
            //         break;
            //     case WaysToStopRays.StopAllRaysExceptPrevSession:
            //         if (index != sessions.Count - 2)
            //             trendLine.Color = Color.HotPink;
            //         break;
            //     case WaysToStopRays.StopOnlyPreviousSession:
            //         if (index == sessions.Count - 2)
            //             trendLine.Color = Color.Lime;
            //         break;
            // }

            if (referencePrice >= nextSessionBottomPrice && referencePrice <= nextSessionTopPrice)
            {
                switch (InputRaysUntilIntersection)
                {
                    case WaysToStopRays.StopNoRays:
                        trendLine.ExtendToInfinity = true;
                        return;
                    case WaysToStopRays.StopAllRays:
                        trendLine.ExtendToInfinity = false;
                        trendLine.Time2 = nextSession.Model.StartTime;
                        return;
                    case WaysToStopRays.StopAllRaysExceptPrevSession:
                        if (index != sessions.Count - 2)
                        {
                            trendLine.ExtendToInfinity = false;
                            trendLine.Time2 = nextSession.Model.StartTime;;
                        }
                        return;
                    case WaysToStopRays.StopOnlyPreviousSession:
                        if (index == sessions.Count - 2)
                        {
                            trendLine.ExtendToInfinity = false;
                            trendLine.Time2 = nextSession.Model.StartTime;
                        }
                        return;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
        }
    }

    public void PostProcessSinglePrintRays(List<MarketProfileSession> sessions)
    {
        if (InputSPRaysUntilIntersection == WaysToStopRays.StopNoRays)
            return;
        
        if (!InputSinglePrintRays)
            return;
        
        var sortedSessions = sessions.OrderBy(s => s.Model.StartTime).ToList();
        
        for (var i = 0; i < sortedSessions.Count; i++)
        {
            if (sortedSessions[i].SinglePrints == null)
                continue;
            
            // Filter only the trend line rays (not the rectangle blocks) from SinglePrints.
            foreach (var obj in sortedSessions[i].SinglePrints)
            {
                if (obj is ChartTrendLine trendLine && trendLine.Name.Contains(ObjPrefix + "SinglePrintRay"))
                    PostProcessSinglePrintRay(trendLine, i, sortedSessions);
            }
        }
    }

    private void PostProcessSinglePrintRay(ChartTrendLine trendLine, int index, List<MarketProfileSession> sessions)
    {
        // The ray's price level is its Y1 coordinate.
        var referencePrice = trendLine.Y1;
        
        if (index == sessions.Count - 1)
            return;

        for (var i = index + 1; i < sessions.Count; i++)
        {
            var nextSession = sessions[i];
            
            var nextSessionTopPrice = MarketProfileCalculator.GetTopPrice(nextSession.Model.Matrix);
            var nextSessionBottomPrice = MarketProfileCalculator.GetBottomPrice(nextSession.Model.Matrix);

            if (referencePrice >= nextSessionBottomPrice && referencePrice <= nextSessionTopPrice)
            {
                switch (InputSPRaysUntilIntersection)
                {
                    case WaysToStopRays.StopNoRays:
                        trendLine.ExtendToInfinity = true;
                        return;
                    case WaysToStopRays.StopAllRays:
                        trendLine.ExtendToInfinity = false;
                        trendLine.Time2 = nextSession.Model.StartTime;
                        return;
                    case WaysToStopRays.StopAllRaysExceptPrevSession:
                        if (index != sessions.Count - 2)
                        {
                            trendLine.ExtendToInfinity = false;
                            trendLine.Time2 = nextSession.Model.StartTime;
                        }
                        return;
                    case WaysToStopRays.StopOnlyPreviousSession:
                        if (index == sessions.Count - 2)
                        {
                            trendLine.ExtendToInfinity = false;
                            trendLine.Time2 = nextSession.Model.StartTime;
                        }
                        return;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
        }
    }

    public List<ChartObject> RenderKeyValues(MarketProfileModel model)
    {
        var result = new List<ChartObject>();
        
        if (!InputShowKeyValues) 
            return result;
        
        var vah = model.ValueAreaHigh;
        var val = model.ValueAreaLow;
        var pointOfControl = model.PointOfControl;

        var matrixStartTime = DateTime.MinValue.Ticks;
        
        for (var row = 0; row < model.Matrix.GetLength(0); row++)
        {
            if (model.Matrix[row, 0] == null)
                continue;
            
            matrixStartTime = model.Matrix[row, 0].StartTime.Ticks;
            break;
        }
        
        var vahText = Chart.DrawText($"{ObjPrefix}VAH-Text-{matrixStartTime}", $"{vah.ToString(_format)}", model.StartTime, vah, InputKeyValuesColor);
            
        vahText.FontSize = InputKeyValuesSize;
        vahText.HorizontalAlignment = HorizontalAlignment.Left;
        vahText.VerticalAlignment = VerticalAlignment.Center;
            
        var valText = Chart.DrawText($"{ObjPrefix}VAL-Text-{matrixStartTime}", $"{val.ToString(_format)}", model.StartTime, val, InputKeyValuesColor);
        valText.FontSize = InputKeyValuesSize;
        valText.HorizontalAlignment = HorizontalAlignment.Left;
        valText.VerticalAlignment = VerticalAlignment.Center;
            
        var pointOfControlText = Chart.DrawText($"{ObjPrefix}PointOfControl-Text-{matrixStartTime}", $"{pointOfControl.ToString(_format)}", model.StartTime, pointOfControl, InputKeyValuesColor);
        pointOfControlText.FontSize = InputKeyValuesSize;
        pointOfControlText.HorizontalAlignment = HorizontalAlignment.Left;
        pointOfControlText.VerticalAlignment = VerticalAlignment.Center;
        
        result.Add(vahText);
        result.Add(valText);
        result.Add(pointOfControlText);
        
        return result;
    }

    public List<ChartObject> RenderHorizontallyFlippedKeyValues(MarketProfileModel model, MarketProfileSession session)
    {
        var result = new List<ChartObject>();

        if (!InputShowKeyValues)
            return result;

        result = RenderKeyValues(model);

        foreach (var obj in result)
        {
            if (obj is not ChartText text)
                continue;

            text.HorizontalAlignment = HorizontalAlignment.Right;
            text.Time = session.Range.Bars.Last().OpenTime;
        }
        
        return result;
    }

    public List<ChartObject> RenderTpoCounts(MarketProfileModel model)
    {
        var result = new List<ChartObject>();
        
        if (!InputShowTpoCounts)
            return result;
        
        var pointOfControl = model.PointOfControl;
        var pointOfControlRowEndTime = Calculator.GetPointOfControlRowEndTime(model.Matrix);
        
        if (model.TpoCountAbove == -1 || model.TpoCountBelow == -1)
            return result;
        
        var matrixStartTime = model.Matrix.GetLength(0) > 0 ? model.Matrix[0, 0].StartTime.Ticks : DateTime.MinValue.Ticks;
        
        var tpoCountAbove = Chart.DrawText($"{ObjPrefix}TPO-Above-{matrixStartTime}", $"{model.TpoCountAbove}", pointOfControlRowEndTime, pointOfControl, InputTpoCountAboveColor);
        tpoCountAbove.FontSize = InputKeyValuesSize;
        tpoCountAbove.HorizontalAlignment = HorizontalAlignment.Right;
        tpoCountAbove.VerticalAlignment = VerticalAlignment.Top;
        
        var tpoCountBelow = Chart.DrawText($"{ObjPrefix}TPO-Below-{matrixStartTime}", $"{model.TpoCountBelow}", pointOfControlRowEndTime, pointOfControl, InputTpoCountBelowColor);
        tpoCountBelow.FontSize = InputKeyValuesSize;
        tpoCountBelow.HorizontalAlignment = HorizontalAlignment.Right;
        tpoCountBelow.VerticalAlignment = VerticalAlignment.Bottom;
        
        result.Add(tpoCountAbove);
        result.Add(tpoCountBelow);
        
        return result;
    }

    public List<ChartObject> RenderHorizontallyFlippedTpoCounts(MarketProfileModel model, MarketProfileSession session)
    {
        if (!InputShowTpoCounts) 
            return new List<ChartObject>();
        
        var matrix = model.Matrix;
        
        var earliestTime = DateTime.MaxValue;
        var latestTime = DateTime.MinValue;
        
        for (var row = 0; row < matrix.GetLength(0); row++)
        for (var column = 0; column < matrix.GetLength(1); column++)
        {
            var point = matrix[row, column];
            if (point == null) 
                continue;
            
            earliestTime = point.StartTime < earliestTime ? point.StartTime : earliestTime;
            latestTime = point.EndTime > latestTime ? point.EndTime : latestTime;
        }
        
        var startPoint = session.Range.Bars.Last().OpenTime;
        var timeOffset = startPoint - latestTime;
        
        DateTime Flip(DateTime t) => latestTime - (t - earliestTime) + timeOffset;
        
        var pointOfControl = model.PointOfControl;
        var pointOfControlRowEndTime = Calculator.GetPointOfControlRowEndTime(model.Matrix);
        var flippedEnd = Flip(pointOfControlRowEndTime);
        var result = new List<ChartObject>();
        
        if (model.TpoCountAbove == -1 || model.TpoCountBelow == -1)
            return result;
        
        double matrixStartTime = DateTime.MinValue.Ticks;
        
        for (var row = 0; row < model.Matrix.GetLength(0); row++)
        {
            if (model.Matrix[row, 0] == null)
                continue;
            
            matrixStartTime = model.Matrix[row, 0].StartTime.Ticks;
            break;
        }
        
        var tpoCountAbove = Chart.DrawText($"{ObjPrefix}TPO-Above-Flipped-{matrixStartTime}", $"{model.TpoCountAbove}", flippedEnd, pointOfControl, InputTpoCountAboveColor);
        tpoCountAbove.FontSize = InputKeyValuesSize;
        tpoCountAbove.HorizontalAlignment = HorizontalAlignment.Right;
        tpoCountAbove.VerticalAlignment = VerticalAlignment.Top;
        
        var tpoCountBelow = Chart.DrawText($"{ObjPrefix}TPO-Below-Flipped-{matrixStartTime}", $"{model.TpoCountBelow}", flippedEnd, pointOfControl, InputTpoCountBelowColor);
        tpoCountBelow.FontSize = InputKeyValuesSize;
        tpoCountBelow.HorizontalAlignment = HorizontalAlignment.Right;
        tpoCountBelow.VerticalAlignment = VerticalAlignment.Bottom;
        result.Add(tpoCountAbove);
        result.Add(tpoCountBelow);

        return result;
    }

    public List<ChartObject> RenderSinglePrints(MarketProfileModel model)
    {
        return InputShowSinglePrint switch
        {
            SinglePrintType.No => new List<ChartObject>(),
            SinglePrintType.LeftSide => RenderLeftSideSinglePrints(model),
            SinglePrintType.RightSide => RenderRightSideSinglePrints(model),
            _ => throw new ArgumentOutOfRangeException()
        };
    }
    
    public List<ChartObject> RenderRightSideSinglePrints(MarketProfileModel model)
    {
        var result = new List<ChartObject>();
        
        //must draw 1 rectangle per each tpo point
        //must draw 1 ray but going backwards in time

        for (var index = 0; index < model.SinglePrints.Count; index++)
        {
            var singlePrint = model.SinglePrints[index];
            var startIndex = singlePrint.BottomTpoIndex;
            var endIndex = singlePrint.TopTpoIndex;

            for (var i = startIndex; i <= endIndex; i++)
            {
                var tpoBlock = model.Matrix[i, 0];
                var tpoStartTime = tpoBlock.StartTime;
                var tpoEndTime = tpoBlock.EndTime;
                var tpoHigh = tpoBlock.Top;
                var tpoLow = tpoBlock.Bottom;

                result.Add(Chart.DrawRectangle($"{ObjPrefix}SinglePrintBlock-{i}-{model.StartTime}-{index}", tpoStartTime, tpoHigh, tpoEndTime, tpoLow, InputSinglePrintColor));
            }

            //now draw the rays backwards in time, starting from startTime
            var s = singlePrint.StartTime;
            var top = singlePrint.High;
            var bottom = singlePrint.Low;
            //

            result.Add(Chart.DrawTrendLine($"{ObjPrefix}SinglePrintRay-top-{model.StartTime}-{index}", s, top, s.AddYears(-10), bottom, InputSinglePrintColor, InputSinglePrintRayWidth, InputSinglePrintRayStyle));
            result.Add(Chart.DrawTrendLine($"{ObjPrefix}SinglePrintRay-bottom-{model.StartTime}-{index}", s, bottom, s.AddYears(-10), top, InputSinglePrintColor, InputSinglePrintRayWidth, InputSinglePrintRayStyle));
        }

        return result;
    }

    public List<ChartObject> RenderLeftSideSinglePrints(MarketProfileModel model)
    {
        var result = new List<ChartObject>();

        for (var index = 0; index < model.SinglePrints.Count; index++)
        {
            var singlePrint = model.SinglePrints[index];
            //get time of 2 bars behind model.StartTime
            var rectangleStartTime = Bars.OpenTimes[Bars.OpenTimes.GetIndexByTime(model.StartTime) - 2];
            var high = singlePrint.High;
            var low = singlePrint.Low;

            var singlePrintRay = Chart.DrawRectangle($"{ObjPrefix}SinglePrintBlock-{model.StartTime}-{index}", rectangleStartTime, high, model.StartTime, low, InputSinglePrintColor);
            singlePrintRay.Thickness = 0;
            singlePrintRay.IsFilled = true;

            result.Add(singlePrintRay);

            if (InputSinglePrintRays)
            {
                var topLine = Chart.DrawTrendLine($"{ObjPrefix}SinglePrintRay-top-{model.StartTime}-{index}", model.StartTime, high, model.StartTime.AddSeconds(1), high, InputSinglePrintColor, InputSinglePrintRayWidth,
                    InputSinglePrintRayStyle);
                topLine.ExtendToInfinity = true;

                result.Add(topLine);

                var bottomLine = Chart.DrawTrendLine($"{ObjPrefix}SinglePrintRay-bottom-{model.StartTime}-{index}", model.StartTime, low, model.StartTime.AddSeconds(1), low, InputSinglePrintColor,
                    InputSinglePrintRayWidth, InputSinglePrintRayStyle);
                bottomLine.ExtendToInfinity = true;

                result.Add(bottomLine);
            }
        }

        return result;
    }
    
    public ChartObject RenderProminentLine(MarketProfileModel profileModel)
    {
        if (!profileModel.IsProminentLine)
            return null;
        
        var startTime = profileModel.StartTime;
        var endTime = Calculator.GetPointOfControlRowEndTime(profileModel.Matrix);
        var pointOfControl = profileModel.PointOfControl;
        
        var matrixStartTime = profileModel.Matrix.GetLength(0) > 0 ? profileModel.Matrix[0, 0].StartTime.Ticks : DateTime.MinValue.Ticks;
        
        return Chart.DrawTrendLine($"{ObjPrefix}ProminentLine-{matrixStartTime}", startTime, pointOfControl, endTime, pointOfControl, InputProminentMedianColor, InputProminentMedianWidth, InputProminentMedianStyle);
    }

    public ChartObject RenderHorizontallyFlippedProminentLine(MarketProfileModel model, MarketProfileSession session)
    {
        if (!model.IsProminentLine)
            return null;
        
        var pointOfControlRowEndTime = Calculator.GetPointOfControlRowEndTime(model.Matrix);
        var timeSpan = pointOfControlRowEndTime - model.StartTime;
        
        var valueAreaObj = RenderProminentLine(model);
        
        if (valueAreaObj is not ChartTrendLine trendLine)
            return null;
        
        trendLine.Time1 = session.Range.Bars.Last().OpenTime;
        trendLine.Time2 = trendLine.Time1.Add(-timeSpan);
        
        return trendLine;
    }
    
    public void DeleteObjects(IEnumerable<ChartObject> objects)
    {
        if (objects == null)
            return;
        
        foreach (var obj in objects)
        {
            if (obj == null)
                continue;
            
            Chart.RemoveObject(obj.Name);
        }
    }
    
    public void DeleteObject(ChartObject obj)
    {
        if (obj == null)
            return;
        
        Chart.RemoveObject(obj.Name);
    }
    
    public void ClearOutputsTillDate(DateTime start)
    {
        for (int i = 0; Bars.OpenTimes[i] < start; i++)
        {
            OutputDevelopingPoC[i] = double.NaN;
            OutputDevelopingVah[i] = double.NaN;
            OutputDevelopingVaL[i] = double.NaN;
        }
    }
    
    public void ClearOutputsOnRange(DateTime start, DateTime end)
    {
        for (int i = Bars.OpenTimes.GetIndexByTime(start); i < Bars.OpenTimes.GetIndexByTime(end); i++)
        {
            OutputDevelopingPoC[i] = double.NaN;
            OutputDevelopingVah[i] = double.NaN;
            OutputDevelopingVaL[i] = double.NaN;
        }
    }

    public void ClearOutputsAfterDate(DateTime start)
    {
        for (int i = Bars.OpenTimes.GetIndexByTime(start); i < Bars.OpenTimes.Count; i++)
        {
            OutputDevelopingPoC[i] = double.NaN;
            OutputDevelopingVah[i] = double.NaN;
            OutputDevelopingVaL[i] = double.NaN;
        }
    }
    
    public void DeleteAllFromSession(MarketProfileSession session)
    {
        DeleteObjects(session.Profile);
        DeleteObjects(session.ValueArea);
        DeleteObjects(session.ValueAreaRays);
        DeleteObjects(session.MedianRays);
        DeleteObjects(session.KeyValues);
        DeleteObjects(session.TpoCounts);
        DeleteObjects(session.SinglePrints);
        DeleteObject(session.ProminentLine);
                
        session.ClearObjects();
    }
    
    public void DeleteAllSessions(List<MarketProfileSession> sessions)
    {
        foreach (var session in sessions)
            DeleteAllFromSession(session);
    }

    #region SettingsAndResources

    public double ValueAreaPercentage => _resources.ValueAreaPercentage;
    public TimeFrame TimeFrame => _resources.TimeFrame;
    public Bars Bars => _resources.Bars;
    public IndicatorDataSeries OutputDevelopingPoC => _settings.OutputDevelopingPoC;
    public IndicatorDataSeries OutputDevelopingVah => _settings.OutputDevelopingVah;
    public IndicatorDataSeries OutputDevelopingVaL => _settings.OutputDevelopingVaL;

    public Symbol Symbol => _resources.Symbol;
    public Chart Chart => _resources.Chart;
    public double OneTickSize => _resources.OneTickSize;
    public void Print(object message) => _resources.Print(message);
    public bool InputColorBullBear => _settings.InputColorBullBear;
    public Color InputStartColor => _settings.InputStartColor;
    public Color InputEndColor => _settings.InputEndColor;
    public Color InputMedianColor => _settings.InputMedianColor;
    public Color InputValueAreaSidesColor => _settings.InputValueAreaSidesColor;
    public Color InputValueAreaHighLowColor => _settings.InputValueAreaHighLowColor;
    public LineStyle InputMedianStyle => _settings.InputMedianStyle;
    public LineStyle InputMedianRayStyle => _settings.InputMedianRayStyle;
    public LineStyle InputValueAreaSidesStyle => _settings.InputValueAreaSidesStyle;
    public LineStyle InputValueAreaHighLowStyle => _settings.InputValueAreaHighLowStyle;
    public LineStyle InputValueAreaRayHighLowStyle => _settings.InputValueAreaRayHighLowStyle;
    public int InputMedianWidth => _settings.InputMedianWidth;
    public int InputMedianRayWidth => _settings.InputMedianRayWidth;
    public int InputValueAreaSidesWidth => _settings.InputValueAreaSidesWidth;
    public int InputValueAreaHighLowWidth => _settings.InputValueAreaHighLowWidth;
    public int InputValueAreaRayHighLowWidth => _settings.InputValueAreaRayHighLowWidth;
    public SessionsToDrawRays InputShowValueAreaRays => _settings.InputShowValueAreaRays;
    public SessionsToDrawRays InputShowMedianRays => _settings.InputShowMedianRays;
    public WaysToStopRays InputRaysUntilIntersection => _settings.InputRaysUntilIntersection;
    public int InputTimeShiftMinutes => _settings.InputTimeShiftMinutes;
    public bool InputShowKeyValues => _settings.InputShowKeyValues;
    public Color InputKeyValuesColor => _settings.InputKeyValuesColor;
    public int InputKeyValuesSize => _settings.InputKeyValuesSize;
    public SinglePrintType InputShowSinglePrint => _settings.InputShowSinglePrint;
    public Color InputSinglePrintColor => _settings.InputSinglePrintColor;
    public bool InputSinglePrintRays => _settings.InputSinglePrintRays;
    public LineStyle InputSinglePrintRayStyle => _settings.InputSinglePrintRayStyle;
    public int InputSinglePrintRayWidth => _settings.InputSinglePrintRayWidth;
    public WaysToStopRays InputSPRaysUntilIntersection => _settings.InputSPRaysUntilIntersection;
    public Color InputProminentMedianColor => _settings.InputProminentMedianColor;
    public bool InputDrawOnlyHistogramBorder => _settings.InputDrawOnlyHistogramBorder;
    public LineStyle InputProminentMedianStyle => _settings.InputProminentMedianStyle;
    public int InputProminentMedianWidth => _settings.InputProminentMedianWidth;
    public bool InputShowTpoCounts => _settings.InputShowTpoCounts;
    public Color InputTpoCountAboveColor => _settings.InputTpoCountAboveColor;
    public Color InputTpoCountBelowColor => _settings.InputTpoCountBelowColor;
    public MarketProfileCalculator Calculator => _settings.Calculator;
    public string ObjPrefix => _settings.ObjPrefix;

    #endregion
}


================================================
FILE: MarketProfile/MarketProfile/Models/MarketProfileModel.cs
================================================
using System;
using System.Collections.Generic;

namespace cAlgo;

public class MarketProfileModel
{
    //I'm not sure if I should keep the original matrix or just stick with the piled one
    public MatrixPoint[,] Matrix { get; set; }
    public DateTime StartTime { get; set; }
    public DateTime EndTime { get; set; }
    
    public double ValueAreaHigh { get; set; }
    public Dictionary<DateTime, double> DevelopingAreaHigh { get; } = new();
    public double ValueAreaLow { get; set; }
    public Dictionary<DateTime, double> DevelopingAreaLow { get; } = new();
    public double Median { get; set; }
    public double PointOfControl { get; set; }
    public Dictionary<DateTime, double> DevelopingPoC { get; } = new();
    
    /// <summary>
    /// TPO stands for Time Price Opportunity
    /// </summary>
    public int TpoCountAbove { get; set; }
    
    /// <summary>
    /// TPO stands for Time Price Opportunity
    /// </summary>
    public int TpoCountBelow { get; set; }

    public List<SinglePrint> SinglePrints { get; } = new();
    
    /// <summary>
    /// A Prominent line is when the Median TPO is > X% of the total amount of TPOs
    /// </summary>
    public bool IsProminentLine { get; set; }

    // public MarketProfileModel(MatrixPoint[,] matrix, DateTime startTime, DateTime endTime)
    // {
    //     Matrix = matrix;
    //     StartTime = startTime;
    //     EndTime = endTime;
    // }
}

public class SinglePrint
{
    public DateTime StartTime { get; set; }
    public DateTime EndTime { get; set; }
    public double High { get; set; }
    public double Low { get; set; }
    public int TopTpoIndex { get; set; }
    public int BottomTpoIndex { get; set; }

    public SinglePrint(DateTime startTime, DateTime endTime, double high, double low, int topTpoIndex, int bottomTpoIndex)
    {
        StartTime = startTime;
        EndTime = endTime;
        High = high;
        Low = low;
        TopTpoIndex = topTpoIndex;
        BottomTpoIndex = bottomTpoIndex;
    }
}

================================================
FILE: MarketProfile/MarketProfile/Models/MarketProfileSession.cs
================================================
using System.Collections.Generic;
using cAlgo.API;

namespace cAlgo;

public class MarketProfileSession
{
    public SessionRange Range { get; set; }
    public MarketProfileModel Model { get; set; }
    public List<ChartObject> Profile { get; set; }
    public List<ChartObject> ValueArea { get; set; }
    public List<ChartObject> ValueAreaRays { get; set; }
    public List<ChartObject> MedianRays { get; set; }
    public List<ChartObject> KeyValues { get; set; }
    public List<ChartObject> TpoCounts { get; set; }
    public List<ChartObject> SinglePrints { get; set; }
    public ChartObject ProminentLine { get; set; }
    public ChartRectangle Rectangle { get; set; }

    public void ClearObjects()
    {
        Profile?.Clear();
        ValueArea?.Clear();
        ValueAreaRays?.Clear();
        MedianRays?.Clear();
        KeyValues?.Clear();
        TpoCounts?.Clear();
        SinglePrints?.Clear();
        Model.DevelopingPoC?.Clear();
        Model.DevelopingAreaHigh?.Clear();
        Model.DevelopingAreaLow?.Clear();
        ProminentLine = null;
    }
}

================================================
FILE: MarketProfile/MarketProfile/Models/SessionState.cs
================================================
namespace cAlgo;

//Creating a sort of "state machine" with enums, rather than the MQL4 approach
//Possible scenarios:
//- There's no storage for this instance
//  - The indicator is initialized with the values from the parameter
//- There's a storage for this instance
//  - The input-parameter has changed from last run, SessionState needs to be updated with the current input-parameter, this change takes priority over the hotkey-state change
//  - The input-parameter has not changed from last run, if a hotkey was used, SessionState needs to be updated with the last hotkey-state used
//  - The input-parameter has not changed from last run, if a hotkey was not used, SessionState and input-parameter should be the same, no need to update anything

public class SessionState
{
    // public DateTime LastHotkeyStateChanged { get; set; }
    // public DateTime LastParameterStateChanged { get; set; }
    public SessionPeriod LastSessionStateByParameter { get; set; }
    public SessionPeriod LastSessionState { get; set; }
    public Transitions LastTransition { get; set; }

    public override string ToString()
    {
        return $"LastSessionState: {LastSessionState}, LastSessionStateByParameter: {LastSessionStateByParameter}, LastTransition: {LastTransition}";
    }
}

================================================
FILE: MarketProfile/MarketProfile/RangeCalculators/AnnualSessionProfileStrategy.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using cAlgo.API;

namespace cAlgo;

public class AnnualSessionProfileStrategy : ISessionProfileStrategy, IIndicatorResources, IRenderingModesResources
{
    private readonly IIndicatorResources _resources;
    private readonly IRenderingModesResources _renderingModesResources;

    public AnnualSessionProfileStrategy(IIndicatorResources resources, IRenderingModesResources renderingModesResources)
    {
        _resources = resources;
        _renderingModesResources = renderingModesResources;
    }

    public IEnumerable<SessionRange> GetSessionRanges(Bars bars, int sessionsToCount, Color startColor, Color endColor, DateTime? endAt = null)
    {
        var useStartFromDate = false;
        var startFrom = DateTime.MinValue;
        
        if (!InputStartFromCurrentSession)
        {
            if (DateTime.TryParse(InputStartFromDate, out startFrom) && !InputSeamlessScrollingMode)
            {
                useStartFromDate = true;
            }
        }
        
        // Filter bars up to endAt if provided
        IEnumerable<Bar> filteredBars = bars;
        
        var result = new List<SessionRange>();
        // Group by year
        IEnumerable<IGrouping<DateTime, Bar>> grouped;
        if (useStartFromDate)
        {
            grouped = filteredBars.GroupBy(b => new DateTime(b.OpenTime.Year, 1, 1))
                .OrderBy(g => g.Key)
                .Where(b => b.Key >= startFrom)
                .Take(sessionsToCount);
        }
        else
        {
            grouped = filteredBars.GroupBy(b => new DateTime(b.OpenTime.Year, 1, 1))
                .OrderByDescending(g => g.Key)
                .Where(g => !endAt.HasValue || g.Key <= endAt.Value)
                .Take(sessionsToCount);
        }

        foreach (var group in grouped)
        {
            var first = group.First();
            var last = group.Last();
            result.Add(new SessionRange
            {
                Start = first.OpenTime,
                End = last.OpenTime,
                StartColor = startColor,
                EndColor = endColor,
                Bars = group
            });
        }
        return result;
    }
    
    public IEnumerable<SessionRange> GetSessionRanges(Bars bars, Color startColor, Color endColor, DateTime startFrom, DateTime endAt)
    {
        var filteredBars = bars.Where(b => b.OpenTime >= startFrom && b.OpenTime <= endAt);
        var result = new List<SessionRange>();
    
        // Group by year
        var grouped = filteredBars.GroupBy(b => new DateTime(b.OpenTime.Year, 1, 1))
            .OrderBy(g => g.Key);

        foreach (var group in grouped)
        {
            var first = group.First();
            var last = group.Last();
            result.Add(new SessionRange
            {
                Start = first.OpenTime,
                End = last.OpenTime,
                StartColor = startColor,
                EndColor = endColor,
                Bars = group
            });
        }
        return result;
    }

    public TimeFrame TimeFrame => _resources.TimeFrame;
    public string InputStartFromDate => _renderingModesResources.InputStartFromDate;
    public bool InputStartFromCurrentSession => _renderingModesResources.InputStartFromCurrentSession;
    public bool InputSeamlessScrollingMode => _renderingModesResources.InputSeamlessScrollingMode;
    public SatSunSolution InputSaturdaySunday => _renderingModesResources.InputSaturdaySunday;
}



================================================
FILE: MarketProfile/MarketProfile/RangeCalculators/DailySessionProfileStrategy.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using cAlgo.API;

namespace cAlgo;

public class DailySessionProfileStrategy : ISessionProfileStrategy, IIndicatorResources, IRenderingModesResources
{
    private readonly IIndicatorResources _resources;
    private readonly IRenderingModesResources _renderingModesResources;

    public DailySessionProfileStrategy(IIndicatorResources resources, IRenderingModesResources renderingModesResources)
    {
        _resources = resources;
        _renderingModesResources = renderingModesResources;
    }

    public IEnumerable<SessionRange> GetSessionRanges(Bars bars, int sessionsToCount, Color startColor, Color endColor, DateTime? endAt = null)
    {
        var useStartFromDate = false;
        var startFrom = DateTime.MinValue;
        
        if (!InputStartFromCurrentSession)
        {
            if (DateTime.TryParse(InputStartFromDate, out startFrom) && !InputSeamlessScrollingMode)
            {
                useStartFromDate = true;
            }
        }
        
        // Filter bars up to endAt if provided
        IEnumerable<Bar> filteredBars = bars;
        
        // Find last N daily sessions, regardless of chart timeframe
        var result = new List<SessionRange>();
        
        // Handle different weekend solutions
        switch (InputSaturdaySunday)
        {
            case SatSunSolution.IgnoreSaturdaySunday:
                // Filter out Saturday and Sunday before grouping
                filteredBars = bars.Where(b => 
                    b.OpenTime.DayOfWeek != DayOfWeek.Saturday && 
                    b.OpenTime.DayOfWeek != DayOfWeek.Sunday);
                break;
            
            case SatSunSolution.AppendSaturdaySunday:
                // Group by custom date that adds weekend days to the following Monday
                var customGroups = bars
                    .GroupBy(b => {
                        var date = b.OpenTime.Date;
                        if (b.OpenTime.DayOfWeek == DayOfWeek.Saturday || b.OpenTime.DayOfWeek == DayOfWeek.Sunday)
                        {
                            // Calculate days until next Monday
                            int daysUntilMonday = ((int)DayOfWeek.Monday - (int)b.OpenTime.DayOfWeek + 7) % 7;
                            return date.AddDays(daysUntilMonday);
                        }
                        return date;
                    });
                
                IEnumerable<IGrouping<DateTime, Bar>> orderedGroups;
                if (useStartFromDate)
                {
                    orderedGroups = customGroups
                        .OrderBy(g => g.Key)
                        .Where(b => b.Key >= startFrom)
                        .Take(sessionsToCount);
                }
                else
                {
                    orderedGroups = customGroups
                        .OrderByDescending(g => g.Key)
                        .Where(g => !endAt.HasValue || g.Key <= endAt.Value)
                        .Take(sessionsToCount);
                }

                foreach (var group in orderedGroups)
                {
                    var orderedBars = group.OrderBy(b => b.OpenTime).ToList();
                    var first = orderedBars.First();
                    var last = orderedBars.Last();
                    result.Add(new SessionRange
                    {
                        Start = first.OpenTime,
                        End = last.OpenTime.Add(Helpers.GetBarTimeSpan(TimeFrame)),
                        StartColor = startColor,
                        EndColor = endColor,
                        Bars = orderedBars
                    });
                }
                return result;
        }
        
        IEnumerable<IGrouping<DateTime, Bar>> grouped;
        if (useStartFromDate)
        {
            grouped = filteredBars
                .GroupBy(b => b.OpenTime.Date)
                .OrderBy(g => g.Key)
                .Where(b => b.Key >= startFrom)
                .Take(sessionsToCount);
        }
        else
        {
            grouped = filteredBars
                .GroupBy(b => b.OpenTime.Date)
                .OrderByDescending(g => g.Key)
                .Where(g => !endAt.HasValue || g.Key <= endAt.Value)
                .Take(sessionsToCount);
        }
    
        foreach (var group in grouped)
        {
            var first = group.First();
            var last = group.Last();
            //todo I think "End" should be last.OpenTime and that's it
            result.Add(new SessionRange
            {
                Start = first.OpenTime, 
                End = last.OpenTime.Add(Helpers.GetBarTimeSpan(TimeFrame)),
                StartColor = startColor,
                EndColor = endColor,
                Bars = group
            });
        }
        return result;
    }
    
    public IEnumerable<SessionRange> GetSessionRanges(Bars bars, Color startColor, Color endColor, DateTime startFrom, DateTime endAt)
    {
        var filteredBars = bars.Where(b => b.OpenTime >= startFrom && b.OpenTime <= endAt);
        var result = new List<SessionRange>();
        var grouped = filteredBars.GroupBy(b => b.OpenTime.Date)
            .OrderBy(g => g.Key);

        foreach (var group in grouped)
        {
            var first = group.First();
            var last = group.Last();
            result.Add(new SessionRange
            {
                Start = first.OpenTime,
                End = last.OpenTime.Add(Helpers.GetBarTimeSpan(TimeFrame)),
                StartColor = startColor,
                EndColor = endColor,
                Bars = group
            });
        }
        return result;
    }

    public TimeFrame TimeFrame => _resources.TimeFrame;
    public string InputStartFromDate => _renderingModesResources.InputStartFromDate;
    public bool InputStartFromCurrentSession => _renderingModesResources.InputStartFromCurrentSession;
    public bool InputSeamlessScrollingMode => _renderingModesResources.InputSeamlessScrollingMode;
    public SatSunSolution InputSaturdaySunday => _renderingModesResources.InputSaturdaySunday;
}

public class IntradaySessionDefinition
{
    public TimeSpan Start { get; init; }
    public TimeSpan End { get; init; }
    public string Name { get; init; }
    public Color StartColor { get; init; }
    public Color EndColor { get; init; }
}



================================================
FILE: MarketProfile/MarketProfile/RangeCalculators/IRenderingModesResources.cs
================================================
namespace cAlgo;

public interface IRenderingModesResources
{
    string InputStartFromDate { get; }
    bool InputStartFromCurrentSession { get; }
    bool InputSeamlessScrollingMode { get; }
    SatSunSolution InputSaturdaySunday { get; }
}

================================================
FILE: MarketProfile/MarketProfile/RangeCalculators/ISessionProfileStrategy.cs
================================================
using System;
using System.Collections.Generic;
using cAlgo.API;

namespace cAlgo;

public interface ISessionProfileStrategy
{
    // Keep existing method for backward compatibility
    IEnumerable<SessionRange> GetSessionRanges(Bars bars, int sessionsToCount, Color startColor, Color endColor, DateTime? startFrom = null);
    
    // Add new method for date range filtering without sessionsToCount
    IEnumerable<SessionRange> GetSessionRanges(Bars bars, Color startColor, Color endColor, DateTime startFrom, DateTime endAt);
}

================================================
FILE: MarketProfile/MarketProfile/RangeCalculators/IntradaySessionProfileStrategy.cs
================================================
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using cAlgo.API;

namespace cAlgo;

public class IntradaySessionProfileStrategy : ISessionProfileStrategy, IRenderingModesResources
{
    private readonly List<IntradaySessionDefinition> _sessions;
    private readonly IIndicatorResources _resources;
    private readonly IRenderingModesResources _renderingModesResources;

    public IntradaySessionProfileStrategy(List<IntradaySessionDefinition> sessions, IIndicatorResources resources, IRenderingModesResources renderingModesResources)
    {
        _sessions = sessions ?? throw new ArgumentNullException(nameof(sessions));
        _resources = resources;
        _renderingModesResources = renderingModesResources;
    }

    public IEnumerable<SessionRange> GetSessionRanges(Bars bars, int sessionsToCount, Color startColor, Color endColor, DateTime? endAt = null)
    {
        foreach (var session in _sessions)
        {
            var useStartFromDate = false;
            var startFrom = DateTime.MinValue;
            
            if (!InputStartFromCurrentSession)
            {
                if (DateTime.TryParse(InputStartFromDate, out startFrom) && !InputSeamlessScrollingMode)
                {
                    useStartFromDate = true;
                }
            }
            
   
Download .txt
gitextract_kb5h78b7/

├── LICENSE
├── MarketProfile/
│   ├── MarketProfile/
│   │   ├── Calculator/
│   │   │   ├── MarketProfileCalculator.cs
│   │   │   └── MatrixPoint.cs
│   │   ├── Enums/
│   │   │   ├── AlertCheckBar.cs
│   │   │   ├── AlertTypes.cs
│   │   │   ├── Direction.cs
│   │   │   ├── PilingMode.cs
│   │   │   ├── SatSunSolution.cs
│   │   │   ├── SessionPeriod.cs
│   │   │   ├── SessionsToDrawRays.cs
│   │   │   ├── SinglePrintType.cs
│   │   │   ├── Transitions.cs
│   │   │   └── WaysToStopRays.cs
│   │   ├── EventArgs/
│   │   │   └── CalculateEventArgs.cs
│   │   ├── Helpers.cs
│   │   ├── Interfaces/
│   │   │   └── IIndicatorResources.cs
│   │   ├── ManagersAndFeatures/
│   │   │   ├── AlertManager.cs
│   │   │   ├── HideRaysFromInvisibleSessionsFeature.cs
│   │   │   ├── HotkeyStateManager.cs
│   │   │   ├── NewHighLowManager.cs
│   │   │   ├── OutputDevelopingValuesManager.cs
│   │   │   ├── SeamlessScrollingManager.cs
│   │   │   └── SessionChangeManager.cs
│   │   ├── MarketProfile.cs
│   │   ├── MarketProfile.csproj
│   │   ├── MarketProfileRenderer.cs
│   │   ├── Models/
│   │   │   ├── MarketProfileModel.cs
│   │   │   ├── MarketProfileSession.cs
│   │   │   └── SessionState.cs
│   │   ├── RangeCalculators/
│   │   │   ├── AnnualSessionProfileStrategy.cs
│   │   │   ├── DailySessionProfileStrategy.cs
│   │   │   ├── IRenderingModesResources.cs
│   │   │   ├── ISessionProfileStrategy.cs
│   │   │   ├── IntradaySessionProfileStrategy.cs
│   │   │   ├── MonthlySessionProfileStrategy.cs
│   │   │   ├── QuarterlySessionProfileStrategy.cs
│   │   │   ├── RectangleSessionProfileStrategy.cs
│   │   │   ├── SemiannualSessionProfileStrategy.cs
│   │   │   ├── SessionProfileStrategyFactory.cs
│   │   │   ├── SessionRange.cs
│   │   │   └── WeeklySessionProfileStrategy.cs
│   │   └── RectangleModeProcessor.cs
│   └── MarketProfile.sln
├── MarketProfile.mq4
├── MarketProfile.mq5
└── README.md
Download .txt
SYMBOL INDEX (233 symbols across 40 files)

FILE: MarketProfile/MarketProfile/Calculator/MarketProfileCalculator.cs
  type IMarketProfileResources (line 11) | public interface IMarketProfileResources
    method Print (line 22) | void Print(object message);
  class MarketProfileCalculator (line 32) | public class MarketProfileCalculator : IMarketProfileResources
    method MarketProfileCalculator (line 38) | public MarketProfileCalculator(IMarketProfileResources resources)
    method GetBarSection (line 47) | public IEnumerable<Bar> GetBarSection(DateTime startTime, DateTime end...
    method GetHistogramSize (line 58) | public double GetHistogramSize(Bar[] bars, int numberOfSlices)
    method GetSlices (line 67) | public int GetSlices(Bar[] bars)
    method FillAndPileMatrixCalculation (line 83) | public MarketProfileModel FillAndPileMatrixCalculation(Bar[] bars, int...
    method FillAndPileMatrixCalculationCropped (line 172) | public MarketProfileModel FillAndPileMatrixCalculationCropped(
    method FillMatrix (line 278) | [Obsolete("Use FillAndPileMatrixCalculation instead")]
    method PileMatrixPointsToLeft (line 331) | [Obsolete("We are piling the matrix on every bar now, so this is not n...
    method PileMatrixPointsToRight (line 380) | [Obsolete("We are piling the matrix on every bar now, so this is not n...
    method GetPointOfControlPrice (line 423) | public static double GetPointOfControlPrice(MatrixPoint[,] matrix, int...
    method GetPointOfControlRowIndex (line 428) | public static int GetPointOfControlRowIndex(MatrixPoint[,] matrix)
    method GetMedianPrice (line 460) | public double GetMedianPrice(MatrixPoint[,] matrix)
    method GetMedianRowIndex (line 485) | public int GetMedianRowIndex(MatrixPoint[,] matrix)
    method GetMedianRowEndTime (line 508) | public DateTime GetMedianRowEndTime(MatrixPoint[,] matrix)
    method GetPointOfControlRowEndTime (line 524) | public DateTime GetPointOfControlRowEndTime(MatrixPoint[,] matrix)
    method GetValuesAroundPointOfControl (line 540) | public static (int totalTopBlocks, int totalBottomBlocks) GetValuesAro...
    method GetValueArea (line 567) | public (double vahPrice, double valPrice) GetValueArea(MatrixPoint[,] ...
    method IsProminentLine (line 606) | public bool IsProminentLine(MatrixPoint[,] matrix, double prominencePe...
    method GetTopPrice (line 617) | public static double GetTopPrice(MatrixPoint[,] matrix)
    method GetBottomPrice (line 622) | public static double GetBottomPrice(MatrixPoint[,] matrix)
    method GetSinglePrints (line 627) | public static SinglePrint[] GetSinglePrints(MatrixPoint[,] matrix)
    method GetTotalBlocks (line 667) | private static int GetTotalBlocks(MatrixPoint[,] matrix)
    method GetTotalRowPoints (line 683) | private static int GetTotalRowPoints(MatrixPoint[,] matrix, int row)
    method Print (line 706) | public void Print(object message) => _resources.Print(message);

FILE: MarketProfile/MarketProfile/Calculator/MatrixPoint.cs
  class MatrixPoint (line 6) | public class MatrixPoint

FILE: MarketProfile/MarketProfile/Enums/AlertCheckBar.cs
  type AlertCheckBar (line 4) | public enum AlertCheckBar

FILE: MarketProfile/MarketProfile/Enums/AlertTypes.cs
  type AlertTypes (line 4) | public enum AlertTypes

FILE: MarketProfile/MarketProfile/Enums/Direction.cs
  type Direction (line 3) | public enum Direction

FILE: MarketProfile/MarketProfile/Enums/PilingMode.cs
  type PilingMode (line 3) | public enum PilingMode

FILE: MarketProfile/MarketProfile/Enums/SatSunSolution.cs
  type SatSunSolution (line 3) | public enum SatSunSolution

FILE: MarketProfile/MarketProfile/Enums/SessionPeriod.cs
  type SessionPeriod (line 3) | public enum SessionPeriod

FILE: MarketProfile/MarketProfile/Enums/SessionsToDrawRays.cs
  type SessionsToDrawRays (line 3) | public enum SessionsToDrawRays

FILE: MarketProfile/MarketProfile/Enums/SinglePrintType.cs
  type SinglePrintType (line 3) | public enum SinglePrintType

FILE: MarketProfile/MarketProfile/Enums/Transitions.cs
  type Transitions (line 3) | public enum Transitions

FILE: MarketProfile/MarketProfile/Enums/WaysToStopRays.cs
  type WaysToStopRays (line 3) | public enum WaysToStopRays

FILE: MarketProfile/MarketProfile/EventArgs/CalculateEventArgs.cs
  class CalculateEventArgs (line 5) | public class CalculateEventArgs : EventArgs

FILE: MarketProfile/MarketProfile/Helpers.cs
  class Helpers (line 8) | public static class Helpers
    method GenerateColorGradient (line 10) | public static Color[] GenerateColorGradient(Color startColor, Color en...
    method GetBarTimeSpan (line 30) | public static TimeSpan GetBarTimeSpan(TimeFrame timeFrame) =>
    method GroupAdjacent (line 62) | public static List<int[]> GroupAdjacent(int[] input)
    method IsInIntradaySession (line 86) | public static bool IsInIntradaySession(DateTime time, IntradaySessionD...
    method SameSessionDay (line 94) | public static bool SameSessionDay(DateTime sessionStart, DateTime barT...
    method GetWeekNumber (line 101) | public static int GetWeekNumber(DateTime time)
    method GetQuarter (line 108) | public static int GetQuarter(DateTime time)

FILE: MarketProfile/MarketProfile/Interfaces/IIndicatorResources.cs
  type IIndicatorResources (line 5) | public interface IIndicatorResources

FILE: MarketProfile/MarketProfile/ManagersAndFeatures/AlertManager.cs
  type IAlertManagerResources (line 7) | public interface IAlertManagerResources
  class AlertManager (line 35) | public class AlertManager : IAlertManagerResources
    method AlertManager (line 44) | public AlertManager(IAlertManagerResources resources)
    method CheckAlerts (line 53) | public void CheckAlerts(int index)
    method DoAlerts (line 157) | private void DoAlerts(AlertTypes alertType, string objectName)
    method CreateArrowObject (line 196) | public void CreateArrowObject(string name, DateTime time, double price...
    method Alert (line 205) | public void Alert(string alertText)

FILE: MarketProfile/MarketProfile/ManagersAndFeatures/HideRaysFromInvisibleSessionsFeature.cs
  type IHideRaysFromInvisibleSessionsFeatureResources (line 6) | public interface IHideRaysFromInvisibleSessionsFeatureResources
  class HideRaysFromInvisibleSessionsFeature (line 13) | public class HideRaysFromInvisibleSessionsFeature : IHideRaysFromInvisib...
    method HideRaysFromInvisibleSessionsFeature (line 17) | public HideRaysFromInvisibleSessionsFeature(IHideRaysFromInvisibleSess...
    method Start (line 22) | public void Start()
    method ChartZoomChanged (line 29) | private void ChartZoomChanged(ChartZoomEventArgs obj)
    method ChartScrollChanged (line 34) | private void ChartScrollChanged(ChartScrollEventArgs obj)
    method ChangeRaysVisibility (line 39) | public void ChangeRaysVisibility()
    method SetVisibility (line 45) | private static void SetVisibility(MarketProfileSession session, bool i...

FILE: MarketProfile/MarketProfile/ManagersAndFeatures/HotkeyStateManager.cs
  type IHotkeyStateManagerResources (line 8) | public interface IHotkeyStateManagerResources
    method Print (line 11) | void Print(object message);
    method CheckZeroIntradaySessions (line 24) | void CheckZeroIntradaySessions();
    method SetSessionsAndAddMarketProfiles (line 25) | bool SetSessionsAndAddMarketProfiles(int sessionsToCount, DateTime? en...
  class HotkeyStateManager (line 38) | public class HotkeyStateManager : IHotkeyStateManagerResources
    method HotkeyStateManager (line 42) | public HotkeyStateManager(IHotkeyStateManagerResources resources)
    method AddHotkeys (line 47) | public void AddHotkeys()
    method SwitchSessionTo (line 59) | private void SwitchSessionTo(SessionPeriod sessionPeriod)
    method Alert (line 83) | public void Alert(string alertText)
    method Print (line 104) | public void Print(object message) => _resources.Print(message);
    method CheckZeroIntradaySessions (line 114) | public void CheckZeroIntradaySessions() =>
    method SetSessionsAndAddMarketProfiles (line 116) | public bool SetSessionsAndAddMarketProfiles(int sessionsToCount, DateT...

FILE: MarketProfile/MarketProfile/ManagersAndFeatures/NewHighLowManager.cs
  class NewHighLowEventArgs (line 6) | public class NewHighLowEventArgs : EventArgs
  type INewHighLowManagerResources (line 11) | public interface INewHighLowManagerResources
  class NewHighLowManager (line 20) | public class NewHighLowManager : INewHighLowManagerResources
    method NewHighLowManager (line 30) | public NewHighLowManager(INewHighLowManagerResources resources)
    method NewHighLowFeature_CalculateEvent (line 37) | private void NewHighLowFeature_CalculateEvent(object sender, Calculate...

FILE: MarketProfile/MarketProfile/ManagersAndFeatures/OutputDevelopingValuesManager.cs
  type IOutputDevelopingValuesManagerResources (line 8) | public interface IOutputDevelopingValuesManagerResources
    method IsNewSessionRequired (line 25) | bool IsNewSessionRequired(MarketProfileSession lastSession, DateTime n...
  class OutputDevelopingValuesManager (line 32) | public class OutputDevelopingValuesManager :
    method OutputDevelopingValuesManager (line 42) | public OutputDevelopingValuesManager(IOutputDevelopingValuesManagerRes...
    method SetOutputSessions (line 52) | public void SetOutputSessions(int sessionsToCount)
    method OnBarOpened (line 119) | private void OnBarOpened(BarOpenedEventArgs obj)
    method UpdateLastOutputSession (line 150) | private void UpdateLastOutputSession(MarketProfileSession session)
    method ClearOutputsFromSession (line 221) | public void ClearOutputsFromSession(MarketProfileSession session)
    method IsNewSessionRequired (line 255) | public bool IsNewSessionRequired(MarketProfileSession lastSession, Dat...

FILE: MarketProfile/MarketProfile/ManagersAndFeatures/SeamlessScrollingManager.cs
  type ISeamlessScrollingManagerResources (line 8) | public interface ISeamlessScrollingManagerResources
    method SetSessionsAndAddMarketProfiles (line 12) | bool SetSessionsAndAddMarketProfiles(int sessionsToCount, DateTime? en...
    method Print (line 17) | void Print(object message);
    method Sleep (line 18) | void Sleep(TimeSpan timeSpan);
  class SeamlessScrollingManager (line 21) | public class SeamlessScrollingManager : ISeamlessScrollingManagerResources
    method SeamlessScrollingManager (line 33) | public SeamlessScrollingManager(ISeamlessScrollingManagerResources res...
    method Start (line 70) | public void Start()
    method ResetSessions (line 77) | private void ResetSessions()
    method SetSessionsAndAddMarketProfiles (line 105) | public bool SetSessionsAndAddMarketProfiles(int sessionsToCount, DateT...
    method Print (line 112) | public void Print(object message) => _resources.Print(message);
    method Sleep (line 113) | public void Sleep(TimeSpan timeSpan)

FILE: MarketProfile/MarketProfile/ManagersAndFeatures/SessionChangeManager.cs
  type ISessionChangeManagerResources (line 6) | public interface ISessionChangeManagerResources
    method Print (line 18) | void Print(object message);
  class SessionChangeManager (line 21) | public class SessionChangeManager : ISessionChangeManagerResources
    method SessionChangeManager (line 25) | public SessionChangeManager(ISessionChangeManagerResources resources)
    method UpdateSessionStateTo (line 30) | public void UpdateSessionStateTo(SessionPeriod sessionPeriod)
    method LoadSessionState (line 38) | public void LoadSessionState()
    method CheckSessions (line 112) | public bool CheckSessions(SessionPeriod sessionPeriod) =>
    method CheckSession (line 126) | private bool CheckSession(SessionPeriod sessionPeriod)
    method CheckSession (line 150) | public bool CheckSession(SessionPeriod sessionPeriod, TimeFrame lowerT...
    method Print (line 187) | public void Print(object message) => _resources.Print(message);

FILE: MarketProfile/MarketProfile/MarketProfile.cs
  class MarketProfile (line 28) | [Indicator(AccessRights = AccessRights.FullAccess, IsOverlay = true)]
    method Initialize (line 435) | protected override void Initialize()
    method Calculate (line 506) | public override void Calculate(int index)
    method OnException (line 517) | protected override void OnException(Exception exception)
    method AddOrUpdateSessions (line 538) | private void AddOrUpdateSessions()
    method RemoveAllOwnObjects (line 582) | private void RemoveAllOwnObjects()
    method GetHTFBarsForRange (line 607) | private Bar[] GetHTFBarsForRange(DateTime start, DateTime end)
    method ForwardFillDevelopingValues (line 629) | private void ForwardFillDevelopingValues(DateTime sessionStart, DateTi...
    method UpdateLastSession (line 653) | private void UpdateLastSession(MarketProfileSession session)
    method IsNewSessionRequired (line 781) | public bool IsNewSessionRequired(MarketProfileSession lastSession, Dat...
    method SameIntradaySession (line 814) | private bool SameIntradaySession(DateTime sessionStart, DateTime barTime)
    method InitializeOneTickSize (line 829) | private void InitializeOneTickSize()
    method SetSessionsAndAddMarketProfiles (line 879) | public bool SetSessionsAndAddMarketProfiles(int sessionsToCount, DateT...
    method BuildAndRender (line 919) | private bool BuildAndRender(SessionRange range, MarketProfileSession s...
    method GetIntradaySessions (line 1016) | public List<IntradaySessionDefinition> GetIntradaySessions()
    method CheckIsNotStartFromDateAndSeamlessScrollingMode (line 1066) | private void CheckIsNotStartFromDateAndSeamlessScrollingMode()
    method CheckZeroIntradaySessions (line 1074) | public void CheckZeroIntradaySessions()

FILE: MarketProfile/MarketProfile/MarketProfileRenderer.cs
  type IMarketProfileRendererSettings (line 10) | public interface IMarketProfileRendererSettings
  class MarketProfileRenderer (line 58) | public class MarketProfileRenderer : IMarketProfileResources, IMarketPro...
    method MarketProfileRenderer (line 65) | public MarketProfileRenderer(IMarketProfileResources resources, IMarke...
    method RenderProfile (line 73) | public List<ChartObject> RenderProfile(MatrixPoint[,] matrix)
    method RenderHorizontallyFlippedProfile (line 133) | public List<ChartObject> RenderHorizontallyFlippedProfile(MatrixPoint[...
    method RenderValueArea (line 213) | public List<ChartObject> RenderValueArea(MarketProfileModel model)
    method RenderHorizontallyFlippedValueArea (line 246) | public List<ChartObject> RenderHorizontallyFlippedValueArea(MarketProf...
    method RenderPointOfControlRays (line 282) | public List<ChartObject> RenderPointOfControlRays(MarketProfileModel m...
    method RenderDevelopingVals (line 306) | public void RenderDevelopingVals(Dictionary<DateTime, double> developi...
    method ClearDevelopingVals (line 315) | public void ClearDevelopingVals(Dictionary<DateTime, double> developin...
    method RenderDevelopingVahs (line 324) | public void RenderDevelopingVahs(Dictionary<DateTime, double> developi...
    method ClearDevelopingVahs (line 333) | public void ClearDevelopingVahs(Dictionary<DateTime, double> developin...
    method RenderDevelopingPoc (line 342) | public void RenderDevelopingPoc(Dictionary<DateTime, double> developin...
    method ClearDevelopingPoc (line 351) | public void ClearDevelopingPoc(Dictionary<DateTime, double> developing...
    method PostProcessPointOfControlRays (line 360) | public void PostProcessPointOfControlRays(List<MarketProfileSession> s...
    method PostProcessPointOfControlRay (line 371) | public void PostProcessPointOfControlRay(ChartObject pointOfControlRay...
    method RenderValueAreaRays (line 438) | public List<ChartObject> RenderValueAreaRays(MarketProfileModel model,...
    method PostProcessValueAreaRays (line 478) | public void PostProcessValueAreaRays(List<MarketProfileSession> sessions)
    method PostProcessValueAreaRay (line 493) | private void PostProcessValueAreaRay(ChartObject valueAreaRay, int ind...
    method PostProcessSinglePrintRays (line 559) | public void PostProcessSinglePrintRays(List<MarketProfileSession> sess...
    method PostProcessSinglePrintRay (line 583) | private void PostProcessSinglePrintRay(ChartTrendLine trendLine, int i...
    method RenderKeyValues (line 630) | public List<ChartObject> RenderKeyValues(MarketProfileModel model)
    method RenderHorizontallyFlippedKeyValues (line 675) | public List<ChartObject> RenderHorizontallyFlippedKeyValues(MarketProf...
    method RenderTpoCounts (line 696) | public List<ChartObject> RenderTpoCounts(MarketProfileModel model)
    method RenderHorizontallyFlippedTpoCounts (line 727) | public List<ChartObject> RenderHorizontallyFlippedTpoCounts(MarketProf...
    method RenderSinglePrints (line 787) | public List<ChartObject> RenderSinglePrints(MarketProfileModel model)
    method RenderRightSideSinglePrints (line 798) | public List<ChartObject> RenderRightSideSinglePrints(MarketProfileMode...
    method RenderLeftSideSinglePrints (line 835) | public List<ChartObject> RenderLeftSideSinglePrints(MarketProfileModel...
    method RenderProminentLine (line 872) | public ChartObject RenderProminentLine(MarketProfileModel profileModel)
    method RenderHorizontallyFlippedProminentLine (line 886) | public ChartObject RenderHorizontallyFlippedProminentLine(MarketProfil...
    method DeleteObjects (line 905) | public void DeleteObjects(IEnumerable<ChartObject> objects)
    method DeleteObject (line 919) | public void DeleteObject(ChartObject obj)
    method ClearOutputsTillDate (line 927) | public void ClearOutputsTillDate(DateTime start)
    method ClearOutputsOnRange (line 937) | public void ClearOutputsOnRange(DateTime start, DateTime end)
    method ClearOutputsAfterDate (line 947) | public void ClearOutputsAfterDate(DateTime start)
    method DeleteAllFromSession (line 957) | public void DeleteAllFromSession(MarketProfileSession session)
    method DeleteAllSessions (line 971) | public void DeleteAllSessions(List<MarketProfileSession> sessions)
    method Print (line 989) | public void Print(object message) => _resources.Print(message);

FILE: MarketProfile/MarketProfile/Models/MarketProfileModel.cs
  class MarketProfileModel (line 6) | public class MarketProfileModel
  class SinglePrint (line 46) | public class SinglePrint
    method SinglePrint (line 55) | public SinglePrint(DateTime startTime, DateTime endTime, double high, ...

FILE: MarketProfile/MarketProfile/Models/MarketProfileSession.cs
  class MarketProfileSession (line 6) | public class MarketProfileSession
    method ClearObjects (line 20) | public void ClearObjects()

FILE: MarketProfile/MarketProfile/Models/SessionState.cs
  class SessionState (line 12) | public class SessionState
    method ToString (line 20) | public override string ToString()

FILE: MarketProfile/MarketProfile/RangeCalculators/AnnualSessionProfileStrategy.cs
  class AnnualSessionProfileStrategy (line 8) | public class AnnualSessionProfileStrategy : ISessionProfileStrategy, IIn...
    method AnnualSessionProfileStrategy (line 13) | public AnnualSessionProfileStrategy(IIndicatorResources resources, IRe...
    method GetSessionRanges (line 19) | public IEnumerable<SessionRange> GetSessionRanges(Bars bars, int sessi...
    method GetSessionRanges (line 69) | public IEnumerable<SessionRange> GetSessionRanges(Bars bars, Color sta...

FILE: MarketProfile/MarketProfile/RangeCalculators/DailySessionProfileStrategy.cs
  class DailySessionProfileStrategy (line 8) | public class DailySessionProfileStrategy : ISessionProfileStrategy, IInd...
    method DailySessionProfileStrategy (line 13) | public DailySessionProfileStrategy(IIndicatorResources resources, IRen...
    method GetSessionRanges (line 19) | public IEnumerable<SessionRange> GetSessionRanges(Bars bars, int sessi...
    method GetSessionRanges (line 130) | public IEnumerable<SessionRange> GetSessionRanges(Bars bars, Color sta...
  class IntradaySessionDefinition (line 160) | public class IntradaySessionDefinition

FILE: MarketProfile/MarketProfile/RangeCalculators/IRenderingModesResources.cs
  type IRenderingModesResources (line 3) | public interface IRenderingModesResources

FILE: MarketProfile/MarketProfile/RangeCalculators/ISessionProfileStrategy.cs
  type ISessionProfileStrategy (line 7) | public interface ISessionProfileStrategy
    method GetSessionRanges (line 10) | IEnumerable<SessionRange> GetSessionRanges(Bars bars, int sessionsToCo...
    method GetSessionRanges (line 13) | IEnumerable<SessionRange> GetSessionRanges(Bars bars, Color startColor...

FILE: MarketProfile/MarketProfile/RangeCalculators/IntradaySessionProfileStrategy.cs
  class IntradaySessionProfileStrategy (line 10) | public class IntradaySessionProfileStrategy : ISessionProfileStrategy, I...
    method IntradaySessionProfileStrategy (line 16) | public IntradaySessionProfileStrategy(List<IntradaySessionDefinition> ...
    method GetSessionRanges (line 23) | public IEnumerable<SessionRange> GetSessionRanges(Bars bars, int sessi...
    method GetSessionRanges (line 138) | public IEnumerable<SessionRange> GetSessionRanges(Bars bars, Color sta...

FILE: MarketProfile/MarketProfile/RangeCalculators/MonthlySessionProfileStrategy.cs
  class MonthlySessionProfileStrategy (line 8) | public class MonthlySessionProfileStrategy : ISessionProfileStrategy, II...
    method MonthlySessionProfileStrategy (line 13) | public MonthlySessionProfileStrategy(IIndicatorResources resources, IR...
    method GetSessionRanges (line 19) | public IEnumerable<SessionRange> GetSessionRanges(Bars bars, int sessi...
    method GetSessionRanges (line 71) | public IEnumerable<SessionRange> GetSessionRanges(Bars bars, Color sta...

FILE: MarketProfile/MarketProfile/RangeCalculators/QuarterlySessionProfileStrategy.cs
  class QuarterlySessionProfileStrategy (line 8) | public class QuarterlySessionProfileStrategy : ISessionProfileStrategy, ...
    method QuarterlySessionProfileStrategy (line 13) | public QuarterlySessionProfileStrategy(IIndicatorResources resources, ...
    method GetSessionRanges (line 19) | public IEnumerable<SessionRange> GetSessionRanges(Bars bars, int sessi...
    method GetSessionRanges (line 70) | public IEnumerable<SessionRange> GetSessionRanges(Bars bars, Color sta...
    method GetQuarterStart (line 93) | private static DateTime GetQuarterStart(DateTime date)

FILE: MarketProfile/MarketProfile/RangeCalculators/RectangleSessionProfileStrategy.cs
  class RectangleSessionProfileStrategy (line 8) | public class RectangleSessionProfileStrategy : ISessionProfileStrategy
    method RectangleSessionProfileStrategy (line 14) | public RectangleSessionProfileStrategy(IIndicatorResources resources, ...
    method GetSessionRanges (line 21) | public IEnumerable<SessionRange> GetSessionRanges(Bars bars, int sessi...
    method GetSessionRanges (line 35) | public IEnumerable<SessionRange> GetSessionRanges(Bars bars, Color sta...

FILE: MarketProfile/MarketProfile/RangeCalculators/SemiannualSessionProfileStrategy.cs
  class SemiannualSessionProfileStrategy (line 8) | public class SemiannualSessionProfileStrategy : ISessionProfileStrategy,...
    method SemiannualSessionProfileStrategy (line 13) | public SemiannualSessionProfileStrategy(IIndicatorResources resources,...
    method GetSessionRanges (line 19) | public IEnumerable<SessionRange> GetSessionRanges(Bars bars, int sessi...
    method GetSessionRanges (line 71) | public IEnumerable<SessionRange> GetSessionRanges(Bars bars, Color sta...
    method GetSemiannualStart (line 94) | private static DateTime GetSemiannualStart(DateTime date)

FILE: MarketProfile/MarketProfile/RangeCalculators/SessionProfileStrategyFactory.cs
  class SessionProfileStrategyFactory (line 6) | public static class SessionProfileStrategyFactory
    method CreateDaily (line 8) | public static ISessionProfileStrategy CreateDaily(IIndicatorResources ...
    method CreateWeekly (line 13) | public static ISessionProfileStrategy CreateWeekly(IIndicatorResources...
    method CreateMonthly (line 18) | public static ISessionProfileStrategy CreateMonthly(IIndicatorResource...
    method CreateQuarterly (line 23) | public static ISessionProfileStrategy CreateQuarterly(IIndicatorResour...
    method CreateSemiannual (line 28) | public static ISessionProfileStrategy CreateSemiannual(IIndicatorResou...
    method CreateAnnual (line 33) | public static ISessionProfileStrategy CreateAnnual(IIndicatorResources...
    method CreateIntradaySessions (line 38) | public static ISessionProfileStrategy CreateIntradaySessions(List<Intr...
    method CreateRectangle (line 46) | public static ISessionProfileStrategy CreateRectangle(IIndicatorResour...

FILE: MarketProfile/MarketProfile/RangeCalculators/SessionRange.cs
  class SessionRange (line 8) | public class SessionRange
    method ToString (line 16) | public override string ToString()

FILE: MarketProfile/MarketProfile/RangeCalculators/WeeklySessionProfileStrategy.cs
  class WeeklySessionProfileStrategy (line 8) | public class WeeklySessionProfileStrategy : ISessionProfileStrategy, IIn...
    method WeeklySessionProfileStrategy (line 13) | public WeeklySessionProfileStrategy(IIndicatorResources resources, IRe...
    method GetSessionRanges (line 19) | public IEnumerable<SessionRange> GetSessionRanges(Bars bars, int sessi...
    method GetSessionRanges (line 88) | public IEnumerable<SessionRange> GetSessionRanges(Bars bars, Color sta...
    method GetWeekStartMonday (line 111) | private static DateTime GetWeekStartMonday(DateTime date)
    method GetWeekStartSunday (line 117) | private static DateTime GetWeekStartSunday(DateTime date)

FILE: MarketProfile/MarketProfile/RectangleModeProcessor.cs
  type IRectangleModeProcessorResources (line 8) | public interface IRectangleModeProcessorResources
    method Print (line 21) | void Print(object obj);
  class RectangleModeProcessor (line 30) | public class RectangleModeProcessor : IRectangleModeProcessorResources, ...
    method RectangleModeProcessor (line 34) | public RectangleModeProcessor(IRectangleModeProcessorResources resources)
    method ChartObjectsRemoved (line 43) | private void ChartObjectsRemoved(ChartObjectsRemovedEventArgs obj)
    method ChartObjectsMoveEnded (line 61) | private void ChartObjectsMoveEnded(ChartObjectsMoveEndedEventArgs obj)
    method ChartKeyDown (line 104) | private void ChartKeyDown(ChartKeyboardEventArgs obj)
    method BuildAndRender (line 158) | private bool BuildAndRender(SessionRange range, MarketProfileSession s...
    method Print (line 257) | public void Print(object obj) => _resources.Print(obj);
Condensed preview — 46 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (706K chars).
[
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "MarketProfile/MarketProfile/Calculator/MarketProfileCalculator.cs",
    "chars": 24961,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Text;\nusing cA"
  },
  {
    "path": "MarketProfile/MarketProfile/Calculator/MatrixPoint.cs",
    "chars": 377,
    "preview": "using System;\nusing cAlgo.API;\n\nnamespace cAlgo;\n\npublic class MatrixPoint\n{\n    public Direction Direction { get; init;"
  },
  {
    "path": "MarketProfile/MarketProfile/Enums/AlertCheckBar.cs",
    "chars": 90,
    "preview": "\nnamespace cAlgo;\n\npublic enum AlertCheckBar\n{\n    CheckCurrentBar,\n    CheckPreviousBar\n}"
  },
  {
    "path": "MarketProfile/MarketProfile/Enums/AlertTypes.cs",
    "chars": 104,
    "preview": "\nnamespace cAlgo;\n\npublic enum AlertTypes\n{\n    PriceBreak,\n    CandleCloseCrossover,\n    GapCrossover\n}"
  },
  {
    "path": "MarketProfile/MarketProfile/Enums/Direction.cs",
    "chars": 60,
    "preview": "namespace cAlgo;\n\npublic enum Direction\n{\n    Up,\n    Down\n}"
  },
  {
    "path": "MarketProfile/MarketProfile/Enums/PilingMode.cs",
    "chars": 64,
    "preview": "namespace cAlgo;\n\npublic enum PilingMode\n{\n    Left,\n    Right\n}"
  },
  {
    "path": "MarketProfile/MarketProfile/Enums/SatSunSolution.cs",
    "chars": 152,
    "preview": "namespace cAlgo;\n\npublic enum SatSunSolution\n{\n    // Normal sessions\n    SaturdaySundayNormalDays,\n    IgnoreSaturdaySu"
  },
  {
    "path": "MarketProfile/MarketProfile/Enums/SessionPeriod.cs",
    "chars": 154,
    "preview": "namespace cAlgo;\n\npublic enum SessionPeriod\n{\n    Daily,\n    Weekly,\n    Monthly,\n    Quarterly,\n    Semiannual,\n    Ann"
  },
  {
    "path": "MarketProfile/MarketProfile/Enums/SessionsToDrawRays.cs",
    "chars": 181,
    "preview": "namespace cAlgo;\n\npublic enum SessionsToDrawRays\n{\n    None,\n    Previous,\n    Current,\n    PreviousCurrent,\n    // Prev"
  },
  {
    "path": "MarketProfile/MarketProfile/Enums/SinglePrintType.cs",
    "chars": 85,
    "preview": "namespace cAlgo;\n\npublic enum SinglePrintType\n{\n    No,\n    LeftSide,\n    RightSide\n}"
  },
  {
    "path": "MarketProfile/MarketProfile/Enums/Transitions.cs",
    "chars": 107,
    "preview": "namespace cAlgo;\n\npublic enum Transitions\n{\n    Initialized,\n    ChangedByHotkey,\n    ChangedByParameter,\n}"
  },
  {
    "path": "MarketProfile/MarketProfile/Enums/WaysToStopRays.cs",
    "chars": 143,
    "preview": "namespace cAlgo;\n\npublic enum WaysToStopRays\n{\n    StopNoRays,\n    StopAllRays,\n    StopAllRaysExceptPrevSession,\n    St"
  },
  {
    "path": "MarketProfile/MarketProfile/EventArgs/CalculateEventArgs.cs",
    "chars": 194,
    "preview": "using System;\n\nnamespace cAlgo;\n\npublic class CalculateEventArgs : EventArgs\n{\n    public bool IsNewBar { get; set; }\n  "
  },
  {
    "path": "MarketProfile/MarketProfile/Helpers.cs",
    "chars": 4150,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Globalization;\nusing cAlgo.API;\n\nnamespace cAlgo;\n\npublic s"
  },
  {
    "path": "MarketProfile/MarketProfile/Interfaces/IIndicatorResources.cs",
    "chars": 109,
    "preview": "using cAlgo.API;\n\nnamespace cAlgo;\n\npublic interface IIndicatorResources\n{\n    TimeFrame TimeFrame { get; }\n}"
  },
  {
    "path": "MarketProfile/MarketProfile/ManagersAndFeatures/AlertManager.cs",
    "chars": 11712,
    "preview": "using System;\nusing cAlgo.API;\nusing cAlgo.API.Internals;\n\nnamespace cAlgo;\n\npublic interface IAlertManagerResources\n{\n "
  },
  {
    "path": "MarketProfile/MarketProfile/ManagersAndFeatures/HideRaysFromInvisibleSessionsFeature.cs",
    "chars": 1580,
    "preview": "using System.Collections.Generic;\nusing cAlgo.API;\n\nnamespace cAlgo;\n\npublic interface IHideRaysFromInvisibleSessionsFea"
  },
  {
    "path": "MarketProfile/MarketProfile/ManagersAndFeatures/HotkeyStateManager.cs",
    "chars": 5160,
    "preview": "using System;\nusing System.Collections.Generic;\nusing cAlgo.API;\nusing cAlgo.API.Internals;\n\nnamespace cAlgo;\n\npublic in"
  },
  {
    "path": "MarketProfile/MarketProfile/ManagersAndFeatures/NewHighLowManager.cs",
    "chars": 2153,
    "preview": "using System;\nusing cAlgo.API;\n\nnamespace cAlgo;\n\npublic class NewHighLowEventArgs : EventArgs\n{\n    public double Value"
  },
  {
    "path": "MarketProfile/MarketProfile/ManagersAndFeatures/OutputDevelopingValuesManager.cs",
    "chars": 11275,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing cAlgo.API;\n\nnamespace cAlgo;\n\npublic interface "
  },
  {
    "path": "MarketProfile/MarketProfile/ManagersAndFeatures/SeamlessScrollingManager.cs",
    "chars": 3953,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing cAlgo.API;\n\nnamespace cAlgo;\n\npublic interface "
  },
  {
    "path": "MarketProfile/MarketProfile/ManagersAndFeatures/SessionChangeManager.cs",
    "chars": 7800,
    "preview": "using System;\nusing cAlgo.API;\n\nnamespace cAlgo;\n\npublic interface ISessionChangeManagerResources\n{\n    LocalStorage Loc"
  },
  {
    "path": "MarketProfile/MarketProfile/MarketProfile.cs",
    "chars": 48951,
    "preview": "// -------------------------------------------------------------------------------\n//   \n// Displays the Market Profile "
  },
  {
    "path": "MarketProfile/MarketProfile/MarketProfile.csproj",
    "chars": 268,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<Project Sdk=\"Microsoft.NET.Sdk\">\r\n  <PropertyGroup>\r\n    <TargetFramework>net6"
  },
  {
    "path": "MarketProfile/MarketProfile/MarketProfileRenderer.cs",
    "chars": 42760,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing cAlgo.API;\nusing cAlg"
  },
  {
    "path": "MarketProfile/MarketProfile/Models/MarketProfileModel.cs",
    "chars": 2015,
    "preview": "using System;\nusing System.Collections.Generic;\n\nnamespace cAlgo;\n\npublic class MarketProfileModel\n{\n    //I'm not sure "
  },
  {
    "path": "MarketProfile/MarketProfile/Models/MarketProfileSession.cs",
    "chars": 1078,
    "preview": "using System.Collections.Generic;\nusing cAlgo.API;\n\nnamespace cAlgo;\n\npublic class MarketProfileSession\n{\n    public Ses"
  },
  {
    "path": "MarketProfile/MarketProfile/Models/SessionState.cs",
    "chars": 1282,
    "preview": "namespace cAlgo;\n\n//Creating a sort of \"state machine\" with enums, rather than the MQL4 approach\n//Possible scenarios:\n/"
  },
  {
    "path": "MarketProfile/MarketProfile/RangeCalculators/AnnualSessionProfileStrategy.cs",
    "chars": 3531,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing cAlgo.API;\n\nnamespace cAlgo;\n\npublic class Annu"
  },
  {
    "path": "MarketProfile/MarketProfile/RangeCalculators/DailySessionProfileStrategy.cs",
    "chars": 6422,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing cAlgo.API;\n\nnamespace cAlgo;\n\npublic class Dail"
  },
  {
    "path": "MarketProfile/MarketProfile/RangeCalculators/IRenderingModesResources.cs",
    "chars": 242,
    "preview": "namespace cAlgo;\n\npublic interface IRenderingModesResources\n{\n    string InputStartFromDate { get; }\n    bool InputStart"
  },
  {
    "path": "MarketProfile/MarketProfile/RangeCalculators/ISessionProfileStrategy.cs",
    "chars": 530,
    "preview": "using System;\nusing System.Collections.Generic;\nusing cAlgo.API;\n\nnamespace cAlgo;\n\npublic interface ISessionProfileStra"
  },
  {
    "path": "MarketProfile/MarketProfile/RangeCalculators/IntradaySessionProfileStrategy.cs",
    "chars": 7459,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Runtime.Compil"
  },
  {
    "path": "MarketProfile/MarketProfile/RangeCalculators/MonthlySessionProfileStrategy.cs",
    "chars": 3671,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing cAlgo.API;\n\nnamespace cAlgo;\n\npublic class Mont"
  },
  {
    "path": "MarketProfile/MarketProfile/RangeCalculators/QuarterlySessionProfileStrategy.cs",
    "chars": 3838,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing cAlgo.API;\n\nnamespace cAlgo;\n\npublic class Quar"
  },
  {
    "path": "MarketProfile/MarketProfile/RangeCalculators/RectangleSessionProfileStrategy.cs",
    "chars": 1292,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing cAlgo.API;\n\nnamespace cAlgo;\n\npublic class Rect"
  },
  {
    "path": "MarketProfile/MarketProfile/RangeCalculators/SemiannualSessionProfileStrategy.cs",
    "chars": 3839,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing cAlgo.API;\n\nnamespace cAlgo;\n\npublic class Semi"
  },
  {
    "path": "MarketProfile/MarketProfile/RangeCalculators/SessionProfileStrategyFactory.cs",
    "chars": 2240,
    "preview": "using System;\nusing System.Collections.Generic;\n\nnamespace cAlgo;\n\npublic static class SessionProfileStrategyFactory\n{\n "
  },
  {
    "path": "MarketProfile/MarketProfile/RangeCalculators/SessionRange.cs",
    "chars": 460,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing cAlgo.API;\n\nnamespace cAlgo;\n\npublic class Sess"
  },
  {
    "path": "MarketProfile/MarketProfile/RangeCalculators/WeeklySessionProfileStrategy.cs",
    "chars": 4881,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing cAlgo.API;\n\nnamespace cAlgo;\n\npublic class Week"
  },
  {
    "path": "MarketProfile/MarketProfile/RectangleModeProcessor.cs",
    "chars": 11246,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing cAlgo.API;\n\nnamespace cAlgo;\n\npublic interface "
  },
  {
    "path": "MarketProfile/MarketProfile.sln",
    "chars": 990,
    "preview": "\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.30011"
  },
  {
    "path": "MarketProfile.mq4",
    "chars": 213347,
    "preview": "//+------------------------------------------------------------------+\r\n//|                                             "
  },
  {
    "path": "MarketProfile.mq5",
    "chars": 229827,
    "preview": "//+------------------------------------------------------------------+\r\n//|                                             "
  },
  {
    "path": "README.md",
    "chars": 847,
    "preview": "[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)]"
  }
]

About this extraction

This page contains the full source code of the EarnForex/MarketProfile GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 46 files (661.1 KB), approximately 155.6k tokens, and a symbol index with 233 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!