# Cluster Statistic

```
//------------------------------------------------------------------------------
//
// Индикатор ClusterStatistic. Copyright (c) 2023 Tiger Trade Capital AG. All rights reserved.
//
//------------------------------------------------------------------------------
 
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;
using System.Windows;
using TigerTrade.Chart.Base.Enums;
using TigerTrade.Chart.Indicators.Common;
using TigerTrade.Chart.Indicators.Enums;
using TigerTrade.Core.UI.Converters;
using TigerTrade.Core.Utils.Time;
using TigerTrade.Dx;
 
namespace TigerTrade.Chart.Indicators.Custom
{
    [TypeConverter(typeof(EnumDescriptionTypeConverter))]
    [DataContract(Name = "ClusterStatisticPeriodType", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")]
    public enum ClusterStatisticPeriodType
    {
        [EnumMember(Value = "Day"), Description("День")]
        Day,
        [EnumMember(Value = "Week"), Description("Неделя")]
        Week,
        [EnumMember(Value = "Month"), Description("Месяц")]
        Month,
        [EnumMember(Value = "VisibleBars"), Description("Видимые бары")]
        VisibleBars,
        [EnumMember(Value = "AllBars"), Description("Все бары")]
        AllBars
    }
 
    [DataContract(Name = "ClusterStatisticIndicator", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")]
    [Indicator("X_ClusterStatistic", "*ClusterStatistic", false, Type = typeof(ClusterStatisticIndicator))]
    internal sealed class ClusterStatisticIndicator : IndicatorBase
    {
        private ClusterStatisticPeriodType _period;
 
        [DataMember(Name = "Period")]
        [Category("Параметры"), DisplayName("Период")]
        public ClusterStatisticPeriodType Period
        {
            get => _period;
            set
            {
                if (value == _period)
                {
                    return;
                }
 
                _period = value;
 
                OnPropertyChanged();
            }
        }
 
        private bool _minimizeValues;
 
        [DataMember(Name = "MinimizeValues")]
        [Category("Параметры"), DisplayName("Минимизировать значения")]
        public bool MinimizeValues
        {
            get => _minimizeValues;
            set
            {
                if (value == _minimizeValues)
                {
                    return;
                }
 
                _minimizeValues = value;
 
                OnPropertyChanged();
            }
        }
 
        private IndicatorIntParam _roundValuesParam;
 
        [DataMember(Name = "RoundValueParam")]
        public IndicatorIntParam RoundValuesParam
        {
            get => _roundValuesParam ?? (_roundValuesParam = new IndicatorIntParam(0));
            set => _roundValuesParam = value;
        }
 
        [DefaultValue(0)]
        [Category("Параметры"), DisplayName("Округлять значения")]
        public int RoundValues
        {
            get => RoundValuesParam.Get(SettingsLongKey);
            set
            {
                if (!RoundValuesParam.Set(SettingsLongKey, value, -4, 4))
                {
                    return;
                }
 
                OnPropertyChanged();
            }
        }
 
        private bool _ask;
 
        [DataMember(Name = "ShowAsk")]
        [Category("Вывод данных"), DisplayName("Ask")]
        public bool ShowAsk
        {
            get => _ask;
            set
            {
                if (value == _ask)
                {
                    return;
                }
 
                _ask = value;
             
                OnPropertyChanged();
            }
        }
 
        private bool _bid;
 
        [DataMember(Name = "ShowBid")]
        [Category("Вывод данных"), DisplayName("Bid")]
        public bool ShowBid
        {
            get => _bid;
            set
            {
                if (value == _bid)
                {
                    return;
                }
 
                _bid = value;
             
                OnPropertyChanged();
            }
        }
 
        private bool _delta;
 
        [DataMember(Name = "ShowDelta")]
        [Category("Вывод данных"), DisplayName("Delta")]
        public bool ShowDelta
        {
            get => _delta;
            set
            {
                if (value == _delta)
                {
                    return;
                }
 
                _delta = value;
             
                OnPropertyChanged();
            }
        }
 
        private bool _volume;
 
        [DataMember(Name = "ShowVolume")]
        [Category("Вывод данных"), DisplayName("Volume")]
        public bool ShowVolume
        {
            get => _volume;
            set
            {
                if (value == _volume)
                {
                    return;
                }
 
                _volume = value;
             
                OnPropertyChanged();
            }
        }
 
        private bool _trades;
 
        [DataMember(Name = "ShowTrades")]
        [Category("Вывод данных"), DisplayName("Trades")]
        public bool ShowTrades
        {
            get => _trades;
            set
            {
                if (value == _trades)
                {
                    return;
                }
 
                _trades = value;
 
                OnPropertyChanged();
            }
        }
 
        [Browsable(false)]
        public override bool ShowIndicatorValues => false;
 
        [Browsable(false)]
        public override bool ShowIndicatorLabels => false;
 
        [Browsable(false)]
        public override IndicatorCalculation Calculation => IndicatorCalculation.OnEachTick;
 
        public ClusterStatisticIndicator()
        {
            ShowIndicatorTitle = false;
 
            Period = ClusterStatisticPeriodType.Day;
 
            MinimizeValues = false;
 
            ShowAsk = true;
            ShowBid = true;
            ShowDelta = true;
            ShowVolume = true;
            ShowTrades = true;
        }
 
        protected override void Execute()
        {
        }
 
        public override void Render(DxVisualQueue visual)
        {
            var labels = new List<string>();
 
            if (ShowVolume)
            {
                labels.Add("Volume");
            }
 
            if (ShowTrades)
            {
                labels.Add("Trades");
            }
 
            if (ShowDelta)
            {
                labels.Add("Delta");
            }
 
            if (ShowBid)
            {
                labels.Add("Bid");
            }
 
            if (ShowAsk)
            {
                labels.Add("Ask");
            }
 
            if (labels.Count == 0)
            {
                return;
            }
 
            var symbol = DataProvider.Symbol;
 
            var linePen = new XPen(new XBrush(Canvas.Theme.ChartAxisColor), 1);
 
            var lastSequence = -1;
 
            var maxVolume = 0m;
            var maxTrades = 0L;
            var maxDelta = 0m;
 
            var updateMaxValues = true;
 
            switch (Period)
            {
                case ClusterStatisticPeriodType.AllBars:
                {
                    updateMaxValues = false;
 
                    var count = DataProvider.Count;
 
                    for (var i = 0; i < count; i++)
                    {
                        var cluster = DataProvider.GetCluster(i);
 
                        if (cluster == null)
                        {
                            continue;
                        }
 
                        var maxValues = cluster.MaxValues;
 
                        maxVolume = Math.Max(maxVolume, cluster.Volume);
                        maxTrades = Math.Max(maxTrades, cluster.Trades);
                        maxDelta = Math.Max(maxDelta, Math.Max(maxValues.MaxDelta, -maxValues.MinDelta));
                    }
 
                    break;
                }
                case ClusterStatisticPeriodType.VisibleBars:
                {
                    updateMaxValues = false;
 
                    for (var i = 0; i < Canvas.Count; i++)
                    {
                        var cluster = DataProvider.GetCluster(Canvas.GetIndex(i));
 
                        if (cluster == null)
                        {
                            continue;
                        }
 
                        var maxValues = cluster.MaxValues;
 
                        maxVolume = Math.Max(maxVolume, cluster.Volume);
                        maxTrades = Math.Max(maxTrades, cluster.Trades);
                        maxDelta = Math.Max(maxDelta, Math.Max(maxValues.MaxDelta, -maxValues.MinDelta));
                    }
 
                    break;
                }
            }
 
            var baseHeight = (int)Canvas.ChartFont.GetHeight() + 4;
 
            var height = labels.Count * baseHeight + 2;
 
            var prevLeft = 0.0;
 
            var columnWidth = Canvas.ColumnWidth;
            var r = Canvas.Rect;
 
            var roundValues = RoundValues;
 
            var timeOffset = TimeHelper.GetSessionOffset(DataProvider.Symbol.Exchange);
 
            for (var i = 0; i < Canvas.Count; i++)
            {
                var x = Canvas.GetXX(i);
 
                var left = x - columnWidth * .5;
 
                if (i == 0)
                {
                    prevLeft = left + columnWidth;
 
                    var backRect = new Rect(new Point(r.Left, r.Bottom - baseHeight*labels.Count - 2),
                        new Point(prevLeft, r.Bottom));
 
                    visual.FillRectangle(XBrush.White, backRect);
                    visual.FillRectangle(Canvas.Theme.ChartBackBrush, backRect);
 
                    for (var j = 1; j <= labels.Count; j++)
                    {
                        var h = baseHeight * j + 2;
 
                        visual.DrawLine(linePen,
                            new Point(r.Left, r.Bottom - h),
                            new Point(x + columnWidth * .5, r.Bottom - h));
                    }
                }
 
                visual.DrawLine(linePen,
                    new Point(left + columnWidth, r.Bottom - height),
                    new Point(left + columnWidth, r.Bottom));
 
                var index = Canvas.GetIndex(i);
 
                var cluster = DataProvider.GetCluster(index);
 
                if (cluster == null)
                {
                    continue;
                }
 
                if (updateMaxValues)
                {
                    var sequence = 1;
 
                    switch (Period)
                    {
                        case ClusterStatisticPeriodType.Day:
 
                            sequence = DataProvider.Period.GetSequence(ChartPeriodType.Day, 1, cluster.Time, timeOffset);
 
                            break;
 
                        case ClusterStatisticPeriodType.Week:
 
                            sequence = DataProvider.Period.GetSequence(ChartPeriodType.Week, 1, cluster.Time, timeOffset);
 
                            break;
 
                        case ClusterStatisticPeriodType.Month:
 
                            sequence = DataProvider.Period.GetSequence(ChartPeriodType.Month, 1, cluster.Time, timeOffset);
 
                            break;
                    }
 
                    if (sequence != lastSequence)
                    {
                        lastSequence = sequence;
 
                        maxVolume = 0;
                        maxTrades = 0;
                        maxDelta = 0;
 
                        for (var j = index; j >= 0; j--)
                        {
                            var cl = DataProvider.GetCluster(j);
 
                            if (cl == null)
                            {
                                continue;
                            }
 
                            var newSequence = 1;
 
                            switch (Period)
                            {
                                case ClusterStatisticPeriodType.Day:
 
                                    newSequence = DataProvider.Period.GetSequence(ChartPeriodType.Day, 1, cl.Time, timeOffset);
 
                                    break;
 
                                case ClusterStatisticPeriodType.Week:
 
                                    newSequence = DataProvider.Period.GetSequence(ChartPeriodType.Week, 1, cl.Time, timeOffset);
 
                                    break;
 
                                case ClusterStatisticPeriodType.Month:
 
                                    newSequence = DataProvider.Period.GetSequence(ChartPeriodType.Month, 1, cl.Time, timeOffset);
 
                                    break;
                            }
 
                            if (newSequence != sequence)
                            {
                                break;
                            }
 
                            var maxValues = cl.MaxValues;
 
                            maxVolume = Math.Max(maxVolume, cl.Volume);
                            maxTrades = Math.Max(maxTrades, cl.Trades);
                            maxDelta = Math.Max(maxDelta, Math.Max(maxValues.MaxDelta, -maxValues.MinDelta));
                        }
                    }
                }
 
                var deltaBrush = new XBrush(
                        (cluster.Delta > 0
                            ? Canvas.Theme.ClusterDeltaPlusColor
                            : Canvas.Theme.ClusterDeltaMinusColor).ChangeOpacity(Math.Abs(cluster.Delta), maxDelta));
 
                var volumeBrush = new XBrush(Canvas.Theme.ClusterVolumeColor.ChangeOpacity(cluster.Volume, maxVolume));
                var tradesBrush = new XBrush(Canvas.Theme.ClusterTradesColor.ChangeOpacity(cluster.Trades, maxTrades));
                var textBrush = new XBrush(Canvas.Theme.ClusterTextColor);
 
                for (var j = 1; j <= labels.Count; j++)
                {
                    var label = labels[j - 1];
 
                    var h = baseHeight * j + 2;
 
                    var cellRect = new Rect(new Point((int)left, r.Bottom - h + 1),
                        new Point((int)prevLeft, r.Bottom - (h - baseHeight) + (j == 1 ? 2 : 0)));
 
                    var cellTextRect = new Rect(new Point(left + 2, r.Bottom - h),
                        new Point(left + columnWidth - 2, r.Bottom - (h - baseHeight)));
 
                    var value = "";
 
                    switch (label)
                    {
                        case "Volume":
 
                            value = symbol.FormatSize(cluster.Volume, roundValues, MinimizeValues);
 
                            break;
 
                        case "Trades":
 
                            value = symbol.FormatTrades(cluster.Trades, roundValues, MinimizeValues);
 
                            break;
 
                        case "Delta":
 
                            value = symbol.FormatSize(cluster.Delta, roundValues, MinimizeValues);
 
                            break;
 
                        case "Bid":
 
                            value = symbol.FormatSize(cluster.Bid, roundValues, MinimizeValues);
 
                            break;
 
                        case "Ask":
 
                            value = symbol.FormatSize(cluster.Ask, roundValues, MinimizeValues);
 
                            break;
                    }
 
                    if (!string.IsNullOrEmpty(value))
                    {
                        visual.FillRectangle(
                            label == "Volume" ? volumeBrush : (label == "Trades" ? tradesBrush : deltaBrush), cellRect);
 
                        visual.DrawString(value, Canvas.ChartFont, textBrush, cellTextRect);
                    }
                }
 
                prevLeft = left;
            }
 
            // labels
 
            var labelMaxWidth = labels.Aggregate(0,
                (current, label) => (int)Math.Max(current, Canvas.ChartFont.GetWidth(label))) + 5;
 
            visual.FillRectangle(Canvas.Theme.ChartBackBrush,
                new Rect(new Point(r.Left, r.Bottom - height), new Point(r.Left + labelMaxWidth, r.Bottom)));
 
            visual.DrawLine(linePen,
                new Point(r.Left + labelMaxWidth, r.Bottom - height),
                new Point(r.Left + labelMaxWidth, r.Bottom));
 
            var labelHeight = 2;
 
            foreach (var label in labels)
            {
                labelHeight += baseHeight;
 
                visual.DrawLine(linePen, new Point(r.Left, r.Bottom - labelHeight),
                    new Point(r.Left + labelMaxWidth, r.Bottom - labelHeight));
 
                var labelTextRect = new Rect(new Point(r.Left + 2, r.Bottom - labelHeight),
                        new Point(r.Left + labelMaxWidth - 2, r.Bottom - (labelHeight - baseHeight)));
 
                visual.DrawString(label, Canvas.ChartFont, Canvas.Theme.ChartFontBrush, labelTextRect);
            }
        }
 
        public override void CopyTemplate(IndicatorBase indicator, bool style)
        {
            var i = (ClusterStatisticIndicator)indicator;
 
            Period = i.Period;
 
            MinimizeValues = i.MinimizeValues;
 
            RoundValuesParam.Copy(i.RoundValuesParam);
 
            ShowAsk = i.ShowAsk;
            ShowBid = i.ShowBid;
            ShowDelta = i.ShowDelta;
            ShowVolume = i.ShowVolume;
            ShowTrades = i.ShowTrades;
 
            base.CopyTemplate(indicator, style);
        }
    }
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://support.tiger.com/razrabotka-dlya-tiger.trade-windows/primery-indikatorov/cluster-statistic.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
