# Trades Flow

```
//------------------------------------------------------------------------------
//
// Индикатор TradesFlow. 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 System.Windows.Media;
using TigerTrade.Chart.Alerts;
using TigerTrade.Chart.Base;
using TigerTrade.Chart.Data;
using TigerTrade.Chart.Indicators.Common;
using TigerTrade.Chart.Indicators.Enums;
using TigerTrade.Dx;
using TigerTrade.Dx.Enums;
 
namespace TigerTrade.Chart.Indicators.Custom
{
    [DataContract(Name = "TradesFlowIndicator", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")]
    [Indicator("X_TradesFlow", "*TradesFlow", true, Type = typeof(TradesFlowIndicator))]
    internal sealed class TradesFlowIndicator : IndicatorBase
    {
        internal sealed class TradesFlowItem
        {
            public DateTime Time { get; set; }
            public decimal Price { get; set; }
            public decimal Price2 { get; set; }
            public decimal Size { get; set; }
            public bool IsBuy { get; set; }
 
            public bool Added { get; set; }
            public bool Alert { get; set; }
 
            public TradesFlowItem(IChartTick trade)
            {
                Time = trade.Time;
                Price = trade.Price;
                Size = trade.Size;
                IsBuy = trade.IsBuy;
            }
        }
 
        internal sealed class TradesFlowList
        {
            private readonly LinkedList<TradesFlowItem> _ticks = new LinkedList<TradesFlowItem>();
 
            public TradesFlowItem Last { get; private set; }
 
            private bool _aggregate;
            private decimal _min;
 
            public void Add(IChartTick tick, bool aggregate, decimal min)
            {
                if (_aggregate != aggregate || _min != min)
                {
                    _aggregate = aggregate;
                    _min = min;
 
                    Last = null;
 
                    _ticks.Clear();
                }
 
                if (Last == null)
                {
                    Last = new TradesFlowItem(tick);
                }
 
                if (aggregate && Last.Time == tick.Time && Last.IsBuy == tick.IsBuy)
                {
                    Last.Size += tick.Size;
                    Last.Price2 = tick.Price;
                }
                else
                {
                    if (Last.Size >= min)
                    {
                        if (!Last.Added)
                        {
                            _ticks.AddFirst(Last);
                        }
                    }
 
                    Last = new TradesFlowItem(tick);
                }
 
                if (Last != null && Last.Size >= min && !Last.Added)
                {
                    Last.Added = true;
 
                    _ticks.AddFirst(Last);
                }
 
                if (_ticks.Count > 500)
                {
                    while (_ticks.Count >= 300)
                    {
                        _ticks.RemoveLast();
                    }
                }
            }
 
            public void Clear()
            {
                Last = null;
 
                _ticks.Clear();
            }
 
            public LinkedList<TradesFlowItem> GetTicks()
            {
                return _ticks;
            }
        }
 
        internal sealed class TickData
        {
            public decimal Size { get; set; }
            public bool IsBuy { get; set; }
            public bool DrawSize { get; set; }
            public bool DrawRect { get; set; }
 
            public double Radius { get; set; }
            public Point Center { get; set; }
            public Rect Rect { get; set; }
        }
 
        private IndicatorDecimalParam _tradesFilterParam;
 
        [DataMember(Name = "TradesFilterParam")]
        public IndicatorDecimalParam TradesFilterParam
        {
            get => _tradesFilterParam ?? (_tradesFilterParam = new IndicatorDecimalParam(0));
            set => _tradesFilterParam = value;
        }
 
        [DefaultValue(null)]
        [Category("Параметры"), DisplayName("Фильтр сделок")]
        public decimal TradesFilter
        {
            get => TradesFilterParam.Get(SettingsShortKey);
            set
            {
                if (!TradesFilterParam.Set(SettingsShortKey, value, 0))
                {
                    return;
                }
 
                OnPropertyChanged();
 
                _tradesFlow?.Clear();
            }
        }
 
        private IndicatorDecimalParam _valuesFilterParam;
 
        [DataMember(Name = "ValuesFilterParam")]
        public IndicatorDecimalParam ValuesFilterParam
        {
            get => _valuesFilterParam ?? (_valuesFilterParam = new IndicatorDecimalParam(5));
            set => _valuesFilterParam = value;
        }
 
        [DefaultValue(5)]
        [Category("Параметры"), DisplayName("Фильтр значений")]
        public decimal ValuesFilter
        {
            get => ValuesFilterParam.Get(SettingsShortKey);
            set
            {
                if (!ValuesFilterParam.Set(SettingsShortKey, value, 0))
                {
                    return;
                }
 
                OnPropertyChanged();
            }
        }
 
        private bool _minimizeValues;
 
        [DataMember(Name = "MinimizeValues")]
        [Category("Параметры"), DisplayName("Минимизировать значения")]
        public bool MinimizeValues
        {
            get => _minimizeValues;
            set
            {
                if (value == _minimizeValues)
                {
                    return;
                }
 
                _minimizeValues = value;
 
                OnPropertyChanged();
            }
        }
 
        private IndicatorIntParam _roundValueParam;
 
        [DataMember(Name = "RoundValueParam")]
        public IndicatorIntParam RoundValuesParam
        {
            get => _roundValueParam ?? (_roundValueParam = new IndicatorIntParam(0));
            set => _roundValueParam = value;
        }
 
        [DefaultValue(0)]
        [Category("Параметры"), DisplayName("Округлять значения")]
        public int RoundValues
        {
            get => RoundValuesParam.Get(SettingsLongKey);
            set
            {
                if (!RoundValuesParam.Set(SettingsLongKey, value, -4, 4))
                {
                    return;
                }
 
                OnPropertyChanged();
            }
        }
 
        private bool _сompactMode;
 
        [DataMember(Name = "CompactMode"), DefaultValue(false)]
        [Category("Параметры"), DisplayName("Компактный режим")]
        public bool CompactMode
        {
            get => _сompactMode;
            set
            {
                if (value == _сompactMode)
                {
                    return;
                }
 
                _сompactMode = value;
 
                OnPropertyChanged();
            }
        }
 
        private bool _aggregateTicks;
 
        [DataMember(Name = "AggregateTicks"), DefaultValue(false)]
        [Category("Параметры"), DisplayName("Агрегировать сделки")]
        public bool AggregateTicks
        {
            get => _aggregateTicks;
            set
            {
                if (value == _aggregateTicks)
                {
                    return;
                }
 
                _aggregateTicks = value;
 
                OnPropertyChanged();
 
                _tradesFlow?.Clear();
            }
        }
 
        private int _width;
 
        [DataMember(Name = "Width"), DefaultValue(30)]
        [Category("Параметры"), DisplayName("Ширина (%)")]
        public int Width
        {
            get => _width;
            set
            {
                value = Math.Max(10, Math.Min(100, value));
 
                if (value == _width)
                {
                    return;
                }
 
                _width = value;
 
                OnPropertyChanged();
            }
        }
 
        private int _offset;
 
        [DataMember(Name = "Offset"), DefaultValue(0)]
        [Category("Параметры"), DisplayName("Отступ справа")]
        public int Offset
        {
            get => _offset;
            set
            {
                value = Math.Max(0, value);
 
                if (value == _offset)
                {
                    return;
                }
 
                _offset = value;
 
                OnPropertyChanged();
            }
        }
 
        private ChartAlertSettings _alert;
 
        [DataMember(Name = "Alert"), Browsable(true)]
        [Category("Параметры"), DisplayName("Оповещение")]
        public ChartAlertSettings Alert
        {
            get => _alert ?? (_alert = new ChartAlertSettings());
            set
            {
                if (Equals(value, _alert))
                {
                    return;
                }
 
                _alert = value;
 
                OnPropertyChanged();
            }
        }
 
        private IndicatorIntParam _signalFilterParam;
 
        [DataMember(Name = "SignalFilterParam")]
        public IndicatorIntParam SignalFilterParam
        {
            get => _signalFilterParam ?? (_signalFilterParam = new IndicatorIntParam(100));
            set => _signalFilterParam = value;
        }
 
        [DefaultValue(100)]
        [Category("Сигнал"), DisplayName("Фильтр")]
        public int SignalFilter
        {
            get => SignalFilterParam.Get(SettingsShortKey);
            set
            {
                if (!SignalFilterParam.Set(SettingsShortKey, value, 0))
                {
                    return;
                }
 
                OnPropertyChanged();
            }
        }
 
        [Browsable(false)]
        public XBrush TickBuyBackBrush { get; private set; }
 
        [Browsable(false)]
        public XBrush TickBuyBorderBrush { get; private set; }
 
        [Browsable(false)]
        public XPen TickBuyBorderPen { get; private set; }
 
        private XColor _tickBuyBorderColor;
 
        [DataMember(Name = "TickBuyBorderColor")]
        [Category("Стиль"), DisplayName("Цвет покупок")]
        public XColor TickBuyColor
        {
            get => _tickBuyBorderColor;
            set
            {
                if (_tickBuyBorderColor == value)
                {
                    return;
                }
 
                _tickBuyBorderColor = value;
 
                TickBuyBackBrush = new XBrush(value);
                TickBuyBorderBrush = new XBrush(new XColor(255, value));
 
                TickBuyBorderPen = new XPen(TickBuyBorderBrush, 1);
 
                OnPropertyChanged();
            }
        }
 
        [Browsable(false)]
        public XBrush TickSellBackBrush { get; private set; }
 
        [Browsable(false)]
        public XBrush TickSellBorderBrush { get; private set; }
 
        [Browsable(false)]
        public XPen TickSellBorderPen { get; private set; }
 
        private XColor _tickSellBorderColor;
 
        [DataMember(Name = "TickSellBorderColor")]
        [Category("Стиль"), DisplayName("Цвет продаж")]
        public XColor TickSellColor
        {
            get => _tickSellBorderColor;
            set
            {
                if (_tickSellBorderColor == value)
                {
                    return;
                }
 
                _tickSellBorderColor = value;
 
                TickSellBackBrush = new XBrush(value);
                TickSellBorderBrush = new XBrush(new XColor(255, value));
 
                TickSellBorderPen = new XPen(TickSellBorderBrush, 1);
 
                OnPropertyChanged();
            }
        }
 
        [Browsable(false)]
        public XBrush TicksLineBrush { get; private set; }
 
        private XColor _ticksLineColor;
 
        [DataMember(Name = "TicksLineColor")]
        [Category("Стиль"), DisplayName("Цвет линии")]
        public XColor TicksLineColor
        {
            get => _ticksLineColor;
            set
            {
                if (_ticksLineColor == value)
                {
                    return;
                }
 
                _ticksLineColor = value;
 
                TicksLineBrush = new XBrush(value);
 
                OnPropertyChanged();
            }
        }
 
        private int _ticksLineWidth;
 
        [DataMember(Name = "TicksLineWidth"), DefaultValue(1)]
        [Category("Стиль"), DisplayName("Толщина линии")]
        public int TicksLineWidth
        {
            get => _ticksLineWidth;
            set
            {
                value = Math.Max(0, Math.Min(10, value));
 
                if (value == _ticksLineWidth)
                {
                    return;
                }
 
                _ticksLineWidth = value;
 
                OnPropertyChanged();
            }
        }
 
        [Browsable(false)]
        public override bool ShowIndicatorValues => false;
 
        [Browsable(false)]
        public override bool ShowIndicatorLabels => false;
 
        [Browsable(false)]
        public override IndicatorCalculation Calculation => IndicatorCalculation.OnEachTick;
 
        private TradesFlowList _tradesFlow;
     
        public TradesFlowIndicator()
        {
            ShowIndicatorTitle = false;
 
            CompactMode = false;
            AggregateTicks = false;
 
            MinimizeValues = false;
 
            Width = 30;
            Offset = 0;
 
            TickBuyColor = Color.FromArgb(127, 70, 130, 180);
            TickSellColor = Color.FromArgb(127, 178, 34, 34);
            TicksLineColor = Color.FromArgb(255, 255, 255, 255);
            TicksLineWidth = 1;
        }
 
        protected override void Execute()
        {
            if (_tradesFlow == null)
            {
                _tradesFlow = new TradesFlowList();
            }
 
            if (ClearData)
            {
                _tradesFlow.Clear();
            }
 
            var ticks = DataProvider.GetTicks();
 
            var tradesFilter = TradesFilter;
            var signalFilter = SignalFilter;
 
            foreach (var tick in ticks)
            {
                _tradesFlow.Add(tick, AggregateTicks, tradesFilter);
 
                var last = _tradesFlow.Last;
 
                if (!Alert.IsActive || last.Size < signalFilter || last.Alert)
                {
                    continue;
                }
 
                last.Alert = true;
 
                var p = DataProvider.Symbol.FormatPrice(last.Price);
 
                AddAlert(Alert, $"TradesFlow: {(last.IsBuy ? "Buy" : "Sell")} {p} x {last.Size}.");
            }
        }
 
        public override void Render(DxVisualQueue visual)
        {
            if (_tradesFlow == null)
            {
                return;
            }
 
            var ticksList = _tradesFlow.GetTicks();
 
            if (ticksList.Count == 0)
            {
                return;
            }
 
            var width = Canvas.Rect.Width / 100.0 * Width;
 
            if (width < 20)
            {
                return;
            }
 
            var start = Canvas.Rect.Right - Offset - 5;
 
            if (start < 20)
            {
                return;
            }
 
            var x = start;
            var i = 0;
 
            var tradesListData = new LinkedList<TickData>();
            var tradePoints = new Point[ticksList.Count];
 
            var symbol = DataProvider.Symbol;
 
            var valuesFilter = ValuesFilter;
            var roundValues = RoundValues;
            var minimizeValues = MinimizeValues;
 
            foreach (var tick in ticksList)
            {
                if (x < start - width)
                {
                    continue;
                }
 
                var radius = 3.0;
                var drawVolume = false;
 
                if (tick.Size >= valuesFilter)
                {
                    var textSize = Canvas.ChartFont.GetSize(symbol.FormatSize(tick.Size, roundValues, minimizeValues));
 
                    if (textSize.Width > textSize.Height)
                    {
                        radius = textSize.Width / 2.0;
                    }
                    else
                    {
                        radius = textSize.Height / 2.0;
                    }
 
                    radius += 4;
 
                    drawVolume = true;
                }
 
                radius += TicksLineWidth / 2.0;
 
                if (x - 2 * radius - 4 < start - width)
                {
                    x -= radius;
 
                    continue;
                }
 
                var moveSize = tick.Size >= valuesFilter
                    ? radius + 1
                    : 1;
 
                x -= moveSize + 1;
 
                var y = GetY(Canvas.IsStock ? (double)tick.Price : 0.5);
 
                var tradeDrawData = new TickData
                {
                    Size = tick.Size,
                    IsBuy = tick.IsBuy,
                    DrawSize = drawVolume,
 
                    Radius = radius,
                    Center = new Point(x, y),
                    Rect = new Rect(x - radius, y - radius, radius * 2, radius * 2),
                };
 
                if (tick.Price2 > 0)
                {
                    var top = GetY(Canvas.IsStock ? (double)Math.Max(tick.Price, tick.Price2) : 0.5) - 10;
                    var bottom = GetY(Canvas.IsStock ? (double)Math.Min(tick.Price, tick.Price2) : 0.5) + 10;
 
                    tradeDrawData.Rect = new Rect(x - radius, top, radius * 2, bottom - top);
 
                    tradeDrawData.DrawRect = true;
                }
 
                tradesListData.AddFirst(tradeDrawData);
 
                tradePoints[i++] = new Point(x, y);
 
                if (!CompactMode)
                {
                    x -= moveSize + TicksLineWidth / 2.0 + 1;
                }
            }
 
            if (TicksLineWidth > 0 && i > 1)
            {
                visual.DrawLines(new XPen(TicksLineBrush, TicksLineWidth), tradePoints.Take(i).ToArray());
            }
 
            foreach (var trade in tradesListData)
            {
                if (!trade.DrawRect)
                {
                    visual.FillEllipse(Canvas.Theme.ChartBackBrush, trade.Center, trade.Radius, trade.Radius);
 
                    visual.FillEllipse(
                        trade.IsBuy
                            ? TickBuyBackBrush
                            : TickSellBackBrush, trade.Center,
                        trade.Radius, trade.Radius);
 
                    visual.DrawEllipse(trade.IsBuy
                            ? TickBuyBorderPen
                            : TickSellBorderPen,
                        trade.Center, trade.Radius, trade.Radius
                    );
                }
                else
                {
                    visual.FillRectangle(Canvas.Theme.ChartBackBrush, trade.Rect);
                    visual.FillRectangle(trade.IsBuy ? TickBuyBackBrush : TickSellBackBrush, trade.Rect);
                    visual.DrawRectangle(trade.IsBuy ? TickBuyBorderPen : TickSellBorderPen, trade.Rect);
                }
 
                if (trade.DrawSize)
                {
                    visual.DrawString(symbol.FormatSize(trade.Size, roundValues, minimizeValues), Canvas.ChartFont,
                        Canvas.Theme.ChartFontBrush, trade.Rect, XTextAlignment.Center);
                }
            }
        }
 
        public override void ApplyColors(IChartTheme theme)
        {
            TickBuyColor = new XColor(127, theme.PaletteColor6);
            TickSellColor = new XColor(127, theme.PaletteColor7);
 
            base.ApplyColors(theme);
        }
 
        public override void CopyTemplate(IndicatorBase indicator, bool style)
        {
            var i = (TradesFlowIndicator)indicator;
 
            CompactMode = i.CompactMode;
            AggregateTicks = i.AggregateTicks;
 
            Alert.Copy(i.Alert, !style);
 
            OnPropertyChanged(nameof(Alert));
 
            TradesFilterParam.Copy(i.TradesFilterParam);
            ValuesFilterParam.Copy(i.ValuesFilterParam);
            RoundValuesParam.Copy(i.RoundValuesParam);
            SignalFilterParam.Copy(i.SignalFilterParam);
 
            Width = i.Width;
            Offset = i.Offset;
 
            TickBuyColor = i.TickBuyColor;
            TickSellColor = i.TickSellColor;
            TicksLineColor = i.TicksLineColor;
            TicksLineWidth = i.TicksLineWidth;
 
            OnPropertyChanged(nameof(TradesFilter));
            OnPropertyChanged(nameof(ValuesFilter));
            OnPropertyChanged(nameof(SignalFilter));
 
            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/trades-flow.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.
