External Chart
/--------------------------------------------------------------------------------
//
// Индикатор ExternalChart. Copyright (c) 2023 Tiger Trade Capital AG. All rights reserved.
//
//--------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.Serialization;
using System.Windows;
using System.Windows.Media;
using TigerTrade.Chart.Base.Enums;
using TigerTrade.Chart.Data;
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 = "ExternalChartPeriodType", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")]
public enum ExternalChartPeriodType
{
[EnumMember(Value = "Minute"), Description("Минута")]
Minute,
[EnumMember(Value = "Hour"), Description("Час")]
Hour,
[EnumMember(Value = "Day"), Description("День")]
Day,
[EnumMember(Value = "Week"), Description("Неделя")]
Week,
[EnumMember(Value = "Month"), Description("Месяц")]
Month
}
[DataContract(Name = "ExternalChartBorderType", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")]
[TypeConverter(typeof(EnumDescriptionTypeConverter))]
public enum ExternalChartBorderType
{
[EnumMember(Value = "None"), Description("Скрыть")]
None,
[EnumMember(Value = "Box"), Description("Коробка")]
Box,
[EnumMember(Value = "ColoredBox"), Description("Коробка с раскраской")]
ColoredBox,
[EnumMember(Value = "Candle"), Description("Свеча")]
Candle,
[EnumMember(Value = "ColoredCandle"), Description("Свеча с раскраской")]
ColoredCandle
}
[DataContract(Name = "ExternalChartIndicator", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")]
[Indicator("Z_ExternalChart", "*ExternalChart", true, Type = typeof(ExternalChartIndicator))]
internal sealed class ExternalChartIndicator : IndicatorBase
{
private ExternalChartPeriodType _periodType;
[DataMember(Name = "PeriodType")]
[Category("Период"), DisplayName("Интервал")]
public ExternalChartPeriodType PeriodType
{
get => _periodType;
set
{
if (value == _periodType)
{
return;
}
_periodType = value;
_periodValue = _periodType == ExternalChartPeriodType.Minute ? 15 : 1;
Clear();
OnPropertyChanged();
OnPropertyChanged(nameof(PeriodValue));
}
}
private int _periodValue;
[DataMember(Name = "PeriodValue")]
[Category("Период"), DisplayName("Значение")]
public int PeriodValue
{
get => _periodValue;
set
{
value = Math.Max(1, value);
if (value == _periodValue)
{
return;
}
_periodValue = value;
Clear();
OnPropertyChanged();
}
}
private bool _showBack;
[DataMember(Name = "ShowBack")]
[Category("Фон"), DisplayName("Отображать фон")]
public bool ShowBack
{
get => _showBack;
set
{
if (value == _showBack)
{
return;
}
_showBack = value;
OnPropertyChanged();
}
}
private XBrush _backBrush;
private XColor _backColor;
[DataMember(Name = "BackColor")]
[Category("Фон"), DisplayName("Цвет фона")]
public XColor BackColor
{
get => _backColor;
set
{
if (value == _backColor)
{
return;
}
_backColor = value;
_backBrush = new XBrush(_backColor);
OnPropertyChanged();
}
}
private bool _showGrid;
[DataMember(Name = "ShowGrid")]
[Category("Сетка"), DisplayName("Отображать сетку")]
public bool ShowGrid
{
get => _showGrid;
set
{
if (value == _showGrid)
{
return;
}
_showGrid = value;
OnPropertyChanged();
}
}
private XBrush _gridBrush;
private XPen _gridPen;
private XColor _gridColor;
[DataMember(Name = "GridColor")]
[Category("Сетка"), DisplayName("Цвет Сетки")]
public XColor GridColor
{
get => _gridColor;
set
{
if (value == _gridColor)
{
return;
}
_gridColor = value;
_gridBrush = new XBrush(_gridColor);
_gridPen = new XPen(_gridBrush, 1);
OnPropertyChanged();
}
}
private ExternalChartBorderType _borderType;
[DataMember(Name = "BorderType")]
[Category("Граница"), DisplayName("Тип границы")]
public ExternalChartBorderType BorderType
{
get => _borderType;
set
{
if (value == _borderType)
{
return;
}
_borderType = value;
OnPropertyChanged();
}
}
private int _borderWidth;
[DataMember(Name = "BorderWidth")]
[Category("Граница"), DisplayName("Ширина границы")]
public int BorderWidth
{
get => _borderWidth;
set
{
value = Math.Max(1, value);
if (value == _borderWidth)
{
return;
}
_borderWidth = value;
OnPropertyChanged();
}
}
private XBrush _borderBrush;
private XColor _borderColor;
[DataMember(Name = "BorderColor")]
[Category("Граница"), DisplayName("Цвет границы")]
public XColor BorderColor
{
get => _borderColor;
set
{
if (value == _borderColor)
{
return;
}
_borderColor = value;
_borderBrush = new XBrush(_borderColor);
OnPropertyChanged();
}
}
[Browsable(false)]
public override bool ShowIndicatorValues => false;
[Browsable(false)]
public override bool ShowIndicatorLabels => false;
[Browsable(false)]
public override IndicatorCalculation Calculation => IndicatorCalculation.OnPriceChange;
private List<ExternalBar> _bars;
private List<ExternalBar> Bars => _bars ?? (_bars = new List<ExternalBar>());
public ExternalChartIndicator()
{
ShowIndicatorTitle = false;
PeriodType = ExternalChartPeriodType.Hour;
PeriodValue = 1;
ShowBack = true;
BackColor = Color.FromArgb(30, 70, 130, 180);
ShowGrid = false;
GridColor = Color.FromArgb(255, 70, 130, 180);
BorderType = ExternalChartBorderType.ColoredBox;
BorderWidth = 1;
BorderColor = Color.FromArgb(255, 120, 120, 120);
}
private int _lastFullID;
private void Clear()
{
_lastFullID = 0;
Bars.Clear();
}
private int GetSequence(DateTime date, double offset)
{
var periodType = ChartPeriodType.Hour;
switch (PeriodType)
{
case ExternalChartPeriodType.Minute:
periodType = ChartPeriodType.Minute;
break;
case ExternalChartPeriodType.Hour:
periodType = ChartPeriodType.Hour;
break;
case ExternalChartPeriodType.Day:
periodType = ChartPeriodType.Day;
break;
case ExternalChartPeriodType.Week:
periodType = ChartPeriodType.Week;
break;
case ExternalChartPeriodType.Month:
periodType = ChartPeriodType.Month;
break;
}
return DataProvider.Period.GetSequence(periodType, PeriodValue, date, offset);
}
protected override void Execute()
{
if (ClearData)
{
Clear();
}
if (Bars.Count > 0 && !Bars[Bars.Count - 1].Completed)
{
Bars.RemoveAt(Bars.Count - 1);
}
var timeOffset = TimeHelper.GetSessionOffset(DataProvider.Symbol.Exchange);
var lastSequence = -1;
for (var i = _lastFullID; i < DataProvider.Count; i++)
{
var cluster = DataProvider.GetCluster(i);
var currSequence = GetSequence(cluster.Time, timeOffset);
if (Bars.Count == 0 || currSequence != lastSequence)
{
lastSequence = currSequence;
if (Bars.Count > 0 && i > _lastFullID)
{
_lastFullID = i;
Bars[Bars.Count - 1].Completed = true;
}
Bars.Add(new ExternalBar(i));
}
Bars[Bars.Count - 1].Update(cluster, i);
}
}
public override void Render(DxVisualQueue visual)
{
if (Bars.Count == 0)
{
return;
}
var startIndex = Canvas.Stop;
var endIndex = Canvas.Stop + Canvas.Count;
var step = (decimal)DataProvider.Step;
var columnWidth2 = Canvas.ColumnWidth / 2.0;
var prevRight = int.MinValue;
foreach (var bar in Bars)
{
if (bar.EndBar < startIndex || bar.StartBar > endIndex)
{
continue;
}
var x1 = Canvas.GetX(bar.StartBar);
var x2 = Canvas.GetX(bar.EndBar);
var left = (int)(x1 - columnWidth2);
var right = (int)(x2 + columnWidth2 - 1);
if (prevRight != int.MinValue)
{
left = prevRight;
}
prevRight = right;
var centerX = (int)((x1 + x2) / 2.0);
var isUp = bar.Open < bar.Close;
var highY = (int)GetY(bar.High + step / 2m);
var lowY = (int)GetY(bar.Low - step / 2m);
var bodyHighY = isUp
? (int)GetY(bar.Close + step / 2m)
: (int)GetY(bar.Open + step / 2m);
var bodyLowY = !isUp
? (int)GetY(bar.Close - step / 2m)
: (int)GetY(bar.Open - step / 2m);
if (ShowBack)
{
visual.FillRectangle(_backBrush,
new Rect(new Point(left, highY - 1), new Point(right, lowY)));
}
if (ShowGrid)
{
for (var j = bar.High + step; j >= bar.Low; j -= step)
{
var currY = (int)GetY(j - step / 2m) - 1;
visual.DrawLine(_gridPen, new Point(left, currY), new Point(right, currY));
}
for (var j = bar.StartBar; j <= bar.EndBar; j++)
{
var barLeft = j == bar.StartBar ? left : Canvas.GetX(j) - columnWidth2 - 1;
visual.DrawLine(_gridPen, new Point(barLeft, highY - 1), new Point(barLeft, lowY));
if (j == bar.EndBar)
{
visual.DrawLine(_gridPen, new Point(right, highY - 1), new Point(right, lowY));
}
}
}
if (BorderType != ExternalChartBorderType.None)
{
var borderBrush = _borderBrush;
if (BorderType == ExternalChartBorderType.ColoredCandle ||
BorderType == ExternalChartBorderType.ColoredBox)
{
borderBrush = new XBrush(isUp ? Canvas.Theme.ClusterUpBarColor : Canvas.Theme.ClusterDownBarColor);
}
var borderPen = new XPen(borderBrush, BorderWidth);
if (BorderType == ExternalChartBorderType.Candle ||
BorderType == ExternalChartBorderType.ColoredCandle)
{
var correct = (int)Math.Ceiling(_borderWidth / 2.0);
visual.DrawRectangle(borderPen, new Rect(new Point(left + correct, bodyHighY), new Point(right - correct, bodyLowY)));
visual.DrawLine(borderPen, new Point(centerX, highY), new Point(centerX, bodyHighY));
visual.DrawLine(borderPen, new Point(centerX, bodyLowY), new Point(centerX, lowY));
}
else if (BorderType == ExternalChartBorderType.Box ||
BorderType == ExternalChartBorderType.ColoredBox)
{
var correct = (int)Math.Ceiling(_borderWidth / 2.0);
visual.DrawRectangle(borderPen, new Rect(new Point(left + correct, highY), new Point(right - correct, lowY)));
}
}
}
}
public override void CopyTemplate(IndicatorBase indicator, bool style)
{
var i = (ExternalChartIndicator)indicator;
PeriodType = i.PeriodType;
PeriodValue = i.PeriodValue;
ShowBack = i.ShowBack;
BackColor = i.BackColor;
ShowGrid = i.ShowGrid;
GridColor = i.GridColor;
BorderType = i.BorderType;
BorderWidth = i.BorderWidth;
BackColor = i.BorderColor;
base.CopyTemplate(indicator, style);
}
private class ExternalBar
{
public readonly int StartBar;
public int EndBar;
public bool Completed;
public decimal Open;
public decimal High;
public decimal Low;
public decimal Close;
private bool _new;
public ExternalBar(int startBar)
{
StartBar = startBar;
_new = true;
}
public void Update(IChartCluster cluster, int bar)
{
if (_new)
{
Open = cluster.Open;
High = cluster.High;
Low = cluster.Low;
_new = false;
}
else
{
High = Math.Max(High, cluster.High);
Low = Math.Min(Low, cluster.Low);
}
Close = cluster.Close;
EndBar = bar;
}
}
}
}
Last updated