Bar Search
//------------------------------------------------------------------------------
//
// Indicator BarSearch. Copyright (c) 2023 Tiger Trade Capital AG. All rights reserved.
//
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using TigerTrade.Chart.Alerts;
using TigerTrade.Chart.Annotations;
using TigerTrade.Chart.Base;
using TigerTrade.Chart.Indicators.Common;
using TigerTrade.Chart.Indicators.Enums;
using TigerTrade.Chart.Indicators.Sources;
using TigerTrade.Core.UI.Common;
using TigerTrade.Core.UI.Converters;
using TigerTrade.Core.Utils.Time;
using TigerTrade.Dx;
namespace TigerTrade.Chart.Indicators.Custom
{
[TypeConverter(typeof(EnumDescriptionTypeConverter))]
[DataContract(Name = "BarSearchConditions", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")]
public enum BarSearchConditions
{
[EnumMember(Value = "GreaterThenSource"), Description("Источник 1 > Источник 2")]
GreaterThenSource,
[EnumMember(Value = "GreaterThenValue"), Description("Источник 1 >= Значение")]
GreaterThenValue,
[EnumMember(Value = "LessThenSource"), Description("Источник 1 < Источник 2")]
LessThenSource,
[EnumMember(Value = "LessThenValue"), Description("Источник 1 <= Значение")]
LessThenValue,
[EnumMember(Value = "EqualSource"), Description("Источник 1 = Источник 2")]
EqualSource,
[EnumMember(Value = "EqualValue"), Description("Источник 1 = Значение")]
EqualValue,
}
[TypeConverter(typeof(ExpandableObjectConverter)), ReadOnly(true)]
[DataContract(Name = "BarSearchCondition", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")]
internal sealed class BarSearchCondition : INotifyPropertyChanged, IDynamicProperty
{
private BarSearchConditions _conditions;
[DataMember(Name = "Condition")]
[DisplayName("Условие")]
public BarSearchConditions Condition
{
get => _conditions;
set
{
if (Equals(value, _conditions))
{
return;
}
_conditions = value;
OnPropertyChanged();
OnPropertyChanged(nameof(Source2));
OnPropertyChanged(nameof(Value));
}
}
private IndicatorSourceBase _source1;
[DataMember(Name = "Source1")]
[DisplayName("Источник 1")]
public IndicatorSourceBase Source1
{
get => _source1 ?? (_source1 = new StockSource());
set
{
if (value == _source1)
{
return;
}
_source1 = value;
OnPropertyChanged();
}
}
private IndicatorSourceBase _source2;
[DataMember(Name = "Source2")]
[DisplayName("Источник 2")]
public IndicatorSourceBase Source2
{
get => _source2 ?? (_source2 = new StockSource());
set
{
if (value == _source2)
{
return;
}
_source2 = value;
OnPropertyChanged();
}
}
private decimal _value;
[DataMember(Name = "Value")]
[DisplayName("Значение")]
public decimal Value
{
get => _value;
set
{
if (value == _value)
{
return;
}
_value = value;
OnPropertyChanged();
}
}
private double[] _source1Cache;
private double[] _source2Cache;
public void Clear()
{
_source1Cache = null;
_source2Cache = null;
}
private double[] GetSource1(IndicatorsHelper helper, int barIndex)
{
if (_source1Cache != null && _source1Cache.Length - 1 > barIndex)
{
return _source1Cache;
}
_source1Cache = Source1.GetSeries(helper);
if (_source1Cache == null || _source1Cache.Length < barIndex)
{
return null;
}
return _source1Cache;
}
private double[] GetSource2(IndicatorsHelper helper, int barIndex)
{
if (_source2Cache != null && _source2Cache.Length - 1 > barIndex)
{
return _source2Cache;
}
_source2Cache = Source2.GetSeries(helper);
if (_source2Cache == null || _source2Cache.Length < barIndex)
{
return null;
}
return _source2Cache;
}
public bool Check(IndicatorsHelper helper, int barIndex)
{
var source1 = GetSource1(helper, barIndex);
switch (Condition)
{
case BarSearchConditions.GreaterThenSource:
{
var source2 = GetSource2(helper, barIndex);
if (source2 == null)
{
return false;
}
if (source1[barIndex] > source2[barIndex])
{
return true;
}
break;
}
case BarSearchConditions.GreaterThenValue:
{
if (source1[barIndex] >= (double)Value)
{
return true;
}
break;
}
case BarSearchConditions.LessThenSource:
{
var source2 = GetSource2(helper, barIndex);
if (source2 == null)
{
return false;
}
if (source1[barIndex] < source2[barIndex])
{
return true;
}
break;
}
case BarSearchConditions.LessThenValue:
{
if (source1[barIndex] <= (double)Value)
{
return true;
}
break;
}
case BarSearchConditions.EqualSource:
{
var source2 = GetSource2(helper, barIndex);
if (source2 == null)
{
return false;
}
if (source1[barIndex] == source2[barIndex])
{
return true;
}
break;
}
case BarSearchConditions.EqualValue:
{
if (source1[barIndex] == (double)Value)
{
return true;
}
break;
}
}
return false;
}
public void Copy(BarSearchCondition condition)
{
Source1 = condition.Source1.CloneSource();
Source2 = condition.Source2.CloneSource();
Value = condition.Value;
}
[Browsable(false)]
public bool Updated { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
Updated = true;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public override string ToString()
{
return "Условие";
}
public bool GetPropertyHasStandardValues(string propertyName)
{
return false;
}
public bool GetPropertyReadOnly(string propertyName)
{
return false;
}
public IEnumerable<object> GetPropertyStandardValues(string propertyName)
{
return null;
}
public bool GetPropertyVisibility(string propertyName)
{
switch (propertyName)
{
case "Source2":
return Condition == BarSearchConditions.GreaterThenSource ||
Condition == BarSearchConditions.LessThenSource ||
Condition == BarSearchConditions.EqualSource;
case "Value":
return Condition == BarSearchConditions.GreaterThenValue ||
Condition == BarSearchConditions.LessThenValue ||
Condition == BarSearchConditions.EqualValue;
}
return true;
}
}
internal sealed class AlertData
{
public int LastIndex { get; set; }
public DateTime LastTime { get; set; }
public AlertData()
{
Clear();
}
public void Clear()
{
LastIndex = -1;
LastTime = DateTime.MinValue;
}
}
[DataContract(Name = "BarSearchIndicator", Namespace = "http://schemas.datacontract.org/2004/07/TigerTrade.Chart.Indicators.Custom")]
[Indicator("X_BarSearch", "*BarSearch", true, Type = typeof(BarSearchIndicator))]
internal sealed class BarSearchIndicator : IndicatorBase, IContainsConditions
{
private XBrush _selectionBrush;
private XPen _selectionPen;
private XColor _selectionColor;
[DataMember(Name = "SelectionColor")]
[Category("Стиль"), DisplayName("Цвет выделения")]
public XColor SelectionColor
{
get => _selectionColor;
set
{
if (value == _selectionColor)
{
return;
}
_selectionColor = value;
_selectionBrush = new XBrush(_selectionColor);
_selectionPen = new XPen(_selectionBrush, 1);
OnPropertyChanged();
}
}
private List<BarSearchCondition> _conditions;
[DataMember(Name = "Conditions")]
[Category("Параметры"), DisplayName("Условия")]
public List<BarSearchCondition> Conditions
{
get => _conditions ?? (_conditions = new List<BarSearchCondition>());
set => _conditions = value;
}
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();
}
}
[Browsable(false)]
public override bool ShowIndicatorValues => false;
[Browsable(false)]
public override bool ShowIndicatorLabels => false;
[Browsable(false)]
public override IndicatorCalculation Calculation => IndicatorCalculation.OnBarClose;
private int _lastFullID;
private Dictionary<int, bool> _bars;
private Dictionary<int, bool> Bars => _bars ?? (_bars = new Dictionary<int, bool>());
private AlertData _alertData;
private void Clear()
{
_lastFullID = 0;
Bars.Clear();
foreach (var condition in Conditions)
{
condition.Clear();
}
_alertData?.Clear();
}
protected override void Execute()
{
var clear = false;
foreach (var condition in Conditions)
{
if (!condition.Updated)
{
continue;
}
condition.Updated = false;
clear = true;
}
if (clear || ClearData)
{
Clear();
}
for (var i = _lastFullID; i < DataProvider.Count; i++)
{
if (!Bars.ContainsKey(i))
{
Bars.Add(i, false);
}
var result = Conditions.Count > 0;
foreach (var condition in Conditions)
{
if (!condition.Check(Helper, i))
{
result = false;
break;
}
}
Bars[i] = result;
if (result && _lastFullID > 0)
{
AddAlert(i);
}
}
_lastFullID = Math.Max(DataProvider.Count - 2, 0);
}
private void AddAlert(int index)
{
if (_alertData == null)
{
_alertData = new AlertData();
}
if (!Alert.IsActive || _alertData.LastIndex >= index)
{
return;
}
var currTime = TimeHelper.LocalTime;
_alertData.LastIndex = index;
_alertData.LastTime = currTime;
AddAlert(Alert, "BarSearch Alert");
}
public override void ApplyColors(IChartTheme theme)
{
SelectionColor = theme.GetNextColor();
base.ApplyColors(theme);
}
public override void CopyTemplate(IndicatorBase indicator, bool style)
{
var i = (BarSearchIndicator)indicator;
SelectionColor = i.SelectionColor;
Conditions.Clear();
if (!style)
{
foreach (var condition in i.Conditions)
{
var newCondition = new BarSearchCondition();
newCondition.Copy(condition);
Conditions.Add(newCondition);
}
}
Alert.Copy(i.Alert, !style);
OnPropertyChanged(nameof(Alert));
base.CopyTemplate(indicator, style);
}
public XBrush GetBrush(int index, bool isUp)
{
if (index > 0 && index < Bars.Count)
{
if (Bars[index])
{
return _selectionBrush;
}
}
return null;
}
}
}
Last updated