From af234594cc0070687e0071ea3e8e3bab586b4f37 Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Mon, 7 Oct 2024 21:41:26 -0700 Subject: [PATCH] event tests --- Directory.Build.props | 5 +- Tests/Tests.csproj | 1 - Tests/test_eventing.cs | 72 ++++++++++++++ Tests/test_iTBar.cs | 100 +++++++++++++++++-- Tests/test_iTValue.cs | 137 ++++++++++++++++++--------- lib/averages/Frama.cs | 12 ++- lib/averages/Jma.cs | 6 ++ lib/averages/Rema.cs | 6 +- lib/core/AbstractBarBase.cs | 90 ------------------ lib/core/abstractBase.cs | 83 +++++++++++++--- lib/core/tvalue.cs | 4 +- lib/volatility/Atr.cs | 14 +-- notebooks/means.dib | 22 +++-- quantower/Volatility/AtrIndicator.cs | 2 +- quantower/_IndicatorBarBase.cs | 37 ++++---- 15 files changed, 391 insertions(+), 200 deletions(-) create mode 100644 Tests/test_eventing.cs delete mode 100644 lib/core/AbstractBarBase.cs diff --git a/Directory.Build.props b/Directory.Build.props index 83427b1d..6ee76064 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,11 +1,12 @@ - en-US net8.0 + preview + enable enable true - preview + en-US false false true diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 14c4f00a..dee01cd9 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -1,6 +1,5 @@ - net9.0 QuanTAlib.Tests QuanTAlib.Tests diff --git a/Tests/test_eventing.cs b/Tests/test_eventing.cs new file mode 100644 index 00000000..832d990c --- /dev/null +++ b/Tests/test_eventing.cs @@ -0,0 +1,72 @@ +namespace QuanTAlib; + +public class EventingTests +{ + [Fact] + public void VerifyEventBasedCalculations() + { + // Create a random number generator with a fixed seed for reproducibility + var random = new Random(42); + + // Create an input series to hold our random values + var input = new TSeries(); + int p = 10; + + // Create a list of indicator pairs (direct calculation and event-based) + var indicators = new List<(AbstractBase Direct, AbstractBase EventBased)> + { + (new Afirma(p,p,Afirma.WindowType.BlackmanHarris), new Afirma(input, p,p,Afirma.WindowType.BlackmanHarris)), + (new Alma(p), new Alma(input, p)), + (new Convolution([1,2,3,2,1]), new Convolution(input, [1,2,3,2,1])), + (new Dema(p), new Dema(input, p)), + (new Dsma(p), new Dsma(input, p)), + (new Dwma(p), new Dwma(input, p)), + (new Ema(p), new Ema(input, p)), + (new Epma(p), new Epma(input, p)), + (new Frama(p), new Frama(input, p)), + (new Fwma(p), new Fwma(input, p)), + (new Gma(p), new Gma(input, p)), + (new Hma(p), new Hma(input, p)), + (new Htit(), new Htit(input)), + (new Hwma(p), new Hwma(input, p)), + (new Jma(p), new Jma(input, p)), + (new Kama(p), new Kama(input, p)), + (new Ltma(gamma: 0.2), new Ltma(input, gamma: 0.2)), + (new Maaf(p), new Maaf(input, p)), + (new Mama(p), new Mama(input, p)), + (new Mgdi(p), new Mgdi(input, p)), + (new Mma(p), new Mma(input, p)), + (new Qema(k1: 0.2, k2: 0.2, k3: 0.2, k4: 0.2), new Qema(input, k1: 0.2, k2: 0.2, k3: 0.2, k4: 0.2)), + (new Rema(p), new Rema(input, p)), + (new Rma(p), new Rma(input, p)), + + (new Sma(p), new Sma(input, p)), + (new Wma(p), new Wma(input, p)), + (new Rma(p), new Rma(input, p)), + + (new Tema(p), new Tema(input, p)), + (new Kama(2, 30, 6), new Kama(input, 2, 30, 6)), + + (new Zlema(p), new Zlema(input, p)) + }; + + // Generate 200 random values and feed them to both direct and event-based indicators + for (int i = 0; i < 200; i++) + { + double randomValue = random.NextDouble() * 100; + input.Add(randomValue); + + // Calculate direct indicators + foreach (var (direct, _) in indicators) + { + direct.Calc(randomValue); + } + } + + // Compare the results of direct and event-based calculations + foreach (var (direct, eventBased) in indicators) + { + Assert.Equal(direct.Value, eventBased.Value, 9); + } + } +} diff --git a/Tests/test_iTBar.cs b/Tests/test_iTBar.cs index ae0ba3f5..79c8563c 100644 --- a/Tests/test_iTBar.cs +++ b/Tests/test_iTBar.cs @@ -4,14 +4,19 @@ using System.Diagnostics.CodeAnalysis; namespace QuanTAlib; +/// +/// Contains unit tests for bar-based indicators in QuanTAlib. +/// [SuppressMessage("Security", "SCS0005:Weak random number generator.", Justification = "Acceptable for tests")] - public class BarIndicatorTests { private readonly Random rnd; private const int SeriesLen = 1000; private const int Corrections = 100; + /// + /// Initializes a new instance of the BarIndicatorTests class. + /// public BarIndicatorTests() { rnd = new Random((int)DateTime.Now.Ticks); @@ -19,9 +24,14 @@ public class BarIndicatorTests private static readonly ITValue[] indicators = new ITValue[] { - new Atr(period: 14), + new Atr(period: 14), + // Add other TBar-based indicators here }; + /// + /// Tests if the indicator produces consistent results when processing new and updated bars. + /// + /// The indicator to test. [Theory] [MemberData(nameof(GetIndicators))] public void IndicatorIsNew(ITValue indicator) @@ -29,7 +39,7 @@ public class BarIndicatorTests var indicator1 = indicator; var indicator2 = indicator; - MethodInfo calcMethod = indicator.GetType().GetMethod("Calc")!; + MethodInfo calcMethod = FindCalcMethod(indicator.GetType()); if (calcMethod == null) { throw new InvalidOperationException($"Calc method not found for indicator type: {indicator.GetType().Name}"); @@ -37,22 +47,96 @@ public class BarIndicatorTests for (int i = 0; i < SeriesLen; i++) { - TBar item1 = new(Time: DateTime.Now, Open: rnd.Next(-100, 100), High: rnd.Next(-100, 100), Low: rnd.Next(-100, 100), Close: rnd.Next(-100, 100), Volume: rnd.Next(-1000, 1000), IsNew: true); - calcMethod.Invoke(indicator1, new object[] { item1 }); + TBar item1 = GenerateRandomBar(isNew: true); + InvokeCalc(indicator1, calcMethod, item1); for (int j = 0; j < Corrections; j++) { - item1 = new(Time: DateTime.Now, Open: rnd.Next(-100, 100), High: rnd.Next(-100, 100), Low: rnd.Next(-100, 100), Close: rnd.Next(-100, 100), Volume: rnd.Next(-1000, 1000), IsNew: false); - calcMethod.Invoke(indicator1, new object[] { item1 }); + item1 = GenerateRandomBar(isNew: false); + InvokeCalc(indicator1, calcMethod, item1); } var item2 = new TBar(item1.Time, item1.Open, item1.High, item1.Low, item1.Close, item1.Volume, IsNew: true); - calcMethod.Invoke(indicator2, new object[] { item2 }); + InvokeCalc(indicator2, calcMethod, item2); Assert.Equal(indicator1.Value, indicator2.Value); } } + /// + /// Finds the appropriate Calc method for the given indicator type. + /// + /// The type of the indicator. + /// The MethodInfo for the Calc method. + private static MethodInfo FindCalcMethod(Type type) + { + while (type != null && type != typeof(object)) + { + var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly) + .Where(m => m.Name == "Calc") + .ToList(); + + if (methods.Count > 0) + { + // Prefer the method with TBar parameter + var method = methods.FirstOrDefault(m => + { + var parameters = m.GetParameters(); + return parameters.Length == 1 && parameters[0].ParameterType == typeof(TBar); + }); + + // If not found, return the first method + return method ?? methods.First(); + } + + type = type.BaseType!; + } + return null!; + } + + /// + /// Invokes the Calc method on the given indicator with the provided input. + /// + /// The indicator instance. + /// The Calc method to invoke. + /// The input TBar. + private static void InvokeCalc(ITValue indicator, MethodInfo calcMethod, TBar input) + { + var parameters = calcMethod.GetParameters(); + if (parameters.Length == 1) + { + calcMethod.Invoke(indicator, new object[] { input }); + } + else if (parameters.Length == 2) + { + calcMethod.Invoke(indicator, new object[] { input, double.NaN }); + } + else + { + throw new InvalidOperationException($"Invalid number of parameters for Calc method in indicator type: {indicator.GetType().Name}"); + } + } + + /// + /// Generates a random TBar for testing purposes. + /// + /// Indicates whether the generated bar should be marked as new. + /// A randomly generated TBar. + private TBar GenerateRandomBar(bool isNew) + { + double open = rnd.NextDouble() * 200 - 100; + double close = rnd.NextDouble() * 200 - 100; + double high = Math.Max(open, close) + rnd.NextDouble() * 10; + double low = Math.Min(open, close) - rnd.NextDouble() * 10; + long volume = rnd.Next(0, 10000); + + return new TBar(Time: DateTime.Now, Open: open, High: high, Low: low, Close: close, Volume: volume, IsNew: isNew); + } + + /// + /// Provides the list of indicators for parameterized tests. + /// + /// An enumerable of object arrays, each containing an indicator instance. public static IEnumerable GetIndicators() { return indicators.Select(indicator => new object[] { indicator }); diff --git a/Tests/test_iTValue.cs b/Tests/test_iTValue.cs index e7a2c41c..f36bec3e 100644 --- a/Tests/test_iTValue.cs +++ b/Tests/test_iTValue.cs @@ -5,7 +5,6 @@ using System.Diagnostics.CodeAnalysis; namespace QuanTAlib; [SuppressMessage("Security", "SCS0005:Weak random number generator.", Justification = "Acceptable for tests")] - public class IndicatorTests { private readonly Random rnd; @@ -17,49 +16,56 @@ public class IndicatorTests rnd = new Random((int)DateTime.Now.Ticks); } + // skipcq: CS-R1055 private static readonly ITValue[] indicators = { new Ema(period: 10, useSma: true), - new Alma(period: 14, offset: 0.85, sigma: 6), - new Afirma(periods: 4, taps: 4, window: Afirma.WindowType.Blackman), - new Convolution(new double[] { 1.0, 2, 3, 2, 1 }), - new Dema(period: 14), - new Dsma(period: 14), - new Dwma(period: 14), - new Epma(period: 14), - new Frama(period: 14), - new Fwma(period: 14), - new Gma(period: 14), - new Hma(period: 14), - new Hwma(period: 14), - new Kama(period: 14), - new Mama(fastLimit: 0.5, slowLimit: 0.05), - new Mgdi(period: 14), - new Mma(period: 14), - new Qema(), - new Rema(period: 14), - new Rma(period: 14), - new Sinema(period: 14), - new Sma(period: 14), - new Smma(period: 14), - new T3(period: 14), - new Tema(period: 14), - new Trima(period: 14), - new Vidya(shortPeriod: 14, longPeriod: 30, alpha: 0.2), - new Wma(period: 14), - new Zlema(period: 14), - new Entropy(period: 14), - new Kurtosis(period: 14), - new Max(period: 14, decay: 0.01), - new Min(period: 14, decay: 0.01), - new Median(period: 14), - new Mode(period: 14), - new Percentile(period: 14, percent: 50), - new Skew(period: 14), - new Stddev(period: 14), - new Variance(period: 14), - new Zscore(period: 14) + new Alma(period: 14, offset: 0.85, sigma: 6), + new Afirma(periods: 4, taps: 4, window: Afirma.WindowType.Blackman), + new Convolution(new[] { 1.0, 2, 3, 2, 1 }), + new Dema(period: 14), + new Dsma(period: 14), + new Dwma(period: 14), + new Epma(period: 14), + new Frama(period: 14), + new Fwma(period: 14), + new Gma(period: 14), + new Hma(period: 14), + new Hwma(period: 14), + new Kama(period: 14), + new Mama(fastLimit: 0.5, slowLimit: 0.05), + new Mgdi(period: 14), + new Mma(period: 14), + new Qema(), + new Rema(period: 14), + new Rma(period: 14), + new Sinema(period: 14), + new Sma(period: 14), + new Smma(period: 14), + new T3(period: 14), + new Tema(period: 14), + new Trima(period: 14), + new Vidya(shortPeriod: 14, longPeriod: 30, alpha: 0.2), + new Wma(period: 14), + new Zlema(period: 14), + new Curvature(period: 14), + new Entropy(period: 14), + new Kurtosis(period: 14), + new Max(period: 14, decay: 0.01), + new Median(period: 14), + new Min(period: 14, decay: 0.01), + new Median(period: 14), + new Mode(period: 14), + new Percentile(period: 14, percent: 50), + new Skew(period: 14), + new Slope(period: 14), + new Stddev(period: 14), + new Variance(period: 14), + new Zscore(period: 14), + + new Historical(period: 14), + new Realized(period: 14) }; [Theory] @@ -69,7 +75,7 @@ public class IndicatorTests var indicator1 = indicator; var indicator2 = indicator; - MethodInfo calcMethod = indicator.GetType().GetMethod("Calc")!; + MethodInfo calcMethod = FindCalcMethod(indicator.GetType()); if (calcMethod == null) { throw new InvalidOperationException($"Calc method not found for indicator type: {indicator.GetType().Name}"); @@ -78,21 +84,64 @@ public class IndicatorTests for (int i = 0; i < SeriesLen; i++) { TValue item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: true); - calcMethod.Invoke(indicator1, new object[] { item1 }); + InvokeCalc(indicator1, calcMethod, item1); for (int j = 0; j < Corrections; j++) { item1 = new(Time: DateTime.Now, Value: rnd.Next(-100, 100), IsNew: false); - calcMethod.Invoke(indicator1, new object[] { item1 }); + InvokeCalc(indicator1, calcMethod, item1); } var item2 = new TValue(item1.Time, item1.Value, IsNew: true); - calcMethod.Invoke(indicator2, new object[] { item2 }); + InvokeCalc(indicator2, calcMethod, item2); Assert.Equal(indicator1.Value, indicator2.Value); } } + private static MethodInfo FindCalcMethod(Type type) + { + while (type != null && type != typeof(object)) + { + var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly) + .Where(m => m.Name == "Calc") + .ToList(); + + if (methods.Count > 0) + { + // Prefer the method with TValue parameter + var method = methods.FirstOrDefault(m => + { + var parameters = m.GetParameters(); + return parameters.Length == 1 && parameters[0].ParameterType == typeof(TValue); + }); + + // If not found, return the first method + return method ?? methods.First(); + } + + type = type.BaseType!; + } + return null!; + } + + private static void InvokeCalc(ITValue indicator, MethodInfo calcMethod, TValue input) + { + var parameters = calcMethod.GetParameters(); + if (parameters.Length == 1) + { + calcMethod.Invoke(indicator, new object[] { input }); + } + else if (parameters.Length == 2) + { + calcMethod.Invoke(indicator, new object[] { input, double.NaN }); + } + else + { + throw new InvalidOperationException($"Invalid number of parameters for Calc method in indicator type: {indicator.GetType().Name}"); + } + } + public static IEnumerable GetIndicators() { return indicators.Select(indicator => new object[] { indicator }); diff --git a/lib/averages/Frama.cs b/lib/averages/Frama.cs index 76905d02..223b1458 100644 --- a/lib/averages/Frama.cs +++ b/lib/averages/Frama.cs @@ -5,22 +5,26 @@ namespace QuanTAlib; public class Frama : AbstractBase { private readonly int _period; - private readonly double _fc; private readonly CircularBuffer _buffer; private double _lastFrama; private double _prevLastFrama; - public Frama(int period, double fc = 0.5) + public Frama(int period) { if (period < 2) throw new ArgumentException("Period must be at least 2", nameof(period)); _period = period; - _fc = fc; _buffer = new CircularBuffer(period); WarmupPeriod = period; } + public Frama(object source, int period) : this(period) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); + } + public override void Init() { base.Init(); @@ -95,4 +99,4 @@ public class Frama : AbstractBase { return _lastFrama; } -} +} \ No newline at end of file diff --git a/lib/averages/Jma.cs b/lib/averages/Jma.cs index 702fc6db..00b77de8 100644 --- a/lib/averages/Jma.cs +++ b/lib/averages/Jma.cs @@ -35,6 +35,12 @@ public class Jma : AbstractBase Init(); } + public Jma(object source, int period, double phase = 0, int vshort = 10) : this(period, phase, vshort) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); + } + public override void Init() { _upperBand = _lowerBand = _prevMa1 = _prevDet0 = _prevDet1 = _prevJma = 0.0; diff --git a/lib/averages/Rema.cs b/lib/averages/Rema.cs index be9168d1..afa22046 100644 --- a/lib/averages/Rema.cs +++ b/lib/averages/Rema.cs @@ -25,7 +25,11 @@ public class Rema : AbstractBase WarmupPeriod = period; Init(); } - + public Rema(object source, int period, double lambda = 0.5) : this(period, lambda) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); + } public override void Init() { base.Init(); diff --git a/lib/core/AbstractBarBase.cs b/lib/core/AbstractBarBase.cs deleted file mode 100644 index 989c17ef..00000000 --- a/lib/core/AbstractBarBase.cs +++ /dev/null @@ -1,90 +0,0 @@ -namespace QuanTAlib; - -/// -/// Provides a base implementation for financial indicators that work with bar data in the QuanTAlib library. -/// -/// -/// This abstract class implements the iTValue interface and defines common properties -/// and methods used by inheriting indicator types. It handles the basic flow of -/// receiving bar data, performing calculations, and publishing results. -/// -public abstract class AbstractBarBase : ITValue { - public DateTime Time { get; set; } - public double Value { get; set; } - public bool IsNew { get; set; } - public bool IsHot { get; set; } - public TBar Input { get; set; } - public String Name { get; set; } = ""; - public int WarmupPeriod { get; set; } - public TValue Tick => new(Time, Value, IsNew, IsHot); - public event ValueSignal Pub = delegate { }; - protected int _index; - protected double _lastValidValue; - protected AbstractBarBase() { - // Add parameters into constructor if needed - } - - /// - /// Subscribes to bar data updates. - /// - /// The source of the bar data. - /// The event arguments containing the bar data. - public void Sub(object source, in TBarEventArgs args) => Calc(args.Bar); - - /// - /// Initializes the indicator's state. - /// - public virtual void Init() { - _index = 0; - _lastValidValue = 0; - } - - /// - /// Calculates the indicator value based on the input bar. - /// - /// The input bar data. - /// A TValue containing the calculated result. - public virtual TValue Calc(TBar input) { - Input = input; - if (double.IsNaN(input.Close) || double.IsInfinity(input.Close)) { - return Process(new TValue(Time: input.Time, Value: GetLastValid(), IsNew: input.IsNew, IsHot: true)); - } - this.Value = Calculation(); - return Process(new TValue(Time: Input.Time, Value: this.Value, IsNew: Input.IsNew, IsHot: this.IsHot)); - } - - /// - /// Retrieves the last valid calculated value. - /// - /// The last valid value of the indicator. - protected virtual double GetLastValid() { - return this.Value; - } - - /// - /// Manages the state of the indicator based on whether a new bar is being processed. - /// - /// Indicates whether the current input is a new bar. - protected abstract void ManageState(bool isNew); - - /// - /// Performs the actual calculation of the indicator value. - /// - /// The calculated indicator value. - protected abstract double Calculation(); - - /// - /// Processes the calculated value, updates the indicator's own state, - /// and publishes the result through an event. - /// - /// The calculated TValue to process. - /// The processed TValue. - protected virtual TValue Process(TValue value) { - this.Time = value.Time; - this.Value = value.Value; - this.IsNew = value.IsNew; - this.IsHot = value.IsHot; - Pub?.Invoke(this, new ValueEventArgs(value)); - return value; - } -} diff --git a/lib/core/abstractBase.cs b/lib/core/abstractBase.cs index 71b0ca6d..203b8329 100644 --- a/lib/core/abstractBase.cs +++ b/lib/core/abstractBase.cs @@ -15,6 +15,9 @@ public abstract class AbstractBase : ITValue public bool IsNew { get; set; } public bool IsHot { get; set; } public TValue Input { get; set; } + public TValue Input2 { get; set; } + public TBar BarInput { get; set; } + public TBar BarInput2 { get; set; } public String Name { get; set; } = ""; public int WarmupPeriod { get; set; } public TValue Tick => new(Time, Value, IsNew, IsHot); @@ -34,6 +37,11 @@ public abstract class AbstractBase : ITValue /// The argument containing the new data point. public void Sub(object source, in ValueEventArgs args) => Calc(args.Tick); + public void Sub(object source1, object source2, in ValueEventArgs args1, in ValueEventArgs args2) => + Calc(args1.Tick, args2.Tick); + + public void Sub(object source, in TBarEventArgs args) => Calc(args.Bar); + /// /// Initializes the indicator's state. /// @@ -43,24 +51,75 @@ public abstract class AbstractBase : ITValue _lastValidValue = 0; } - /// - /// Calculates the indicator value based on the input. - /// - /// The input value for the calculation. - /// A TValue representing the calculated indicator value. - /// - /// This method calls the specific Calculation() method where the actual implementation is. - /// If the input value is NaN or infinity, it returns the last valid value instead. - /// public virtual TValue Calc(TValue input) { Input = input; - if (double.IsNaN(input.Value) || double.IsInfinity(input.Value)) + Input2 = new(Time: Input.Time, Value: double.NaN, IsNew: Input.IsNew, IsHot: Input.IsHot); + return HandleErrorCalculations(input.Value, input.Time, input.IsNew); + } + + public virtual TValue Calc(TBar barInput) + { + BarInput = barInput; + return HandleErrorCalculations(barInput.Close, barInput.Time, barInput.IsNew); + } + + public virtual TValue Calc(TValue input1, TValue input2) + { + Input = input1; + Input2 = input2; + return HandleErrorCalculations(input1.Value, input2.Value, input1.Time, input1.IsNew); + } + + public virtual TValue Calc(TBar input1, TBar input2) + { + BarInput = input1; + BarInput2 = input2; + return HandleErrorCalculations(input1.Close, input2.Close, input1.Time, input1.IsNew); + } + + /// + /// Handles error calculations and invalid input values. + /// + /// The primary input value to check. + /// The timestamp of the input. + /// Indicates if the input is new. + /// A TValue object with the calculated or last valid value. + /// + /// This method checks for NaN or infinity in the input value. If an invalid value is detected, + /// it returns the last valid value. Otherwise, it proceeds with the calculation. + /// + protected virtual TValue HandleErrorCalculations(double value, DateTime time, bool isNew) + { + if (double.IsNaN(value) || double.IsInfinity(value)) { - return Process(new TValue(input.Time, GetLastValid(), input.IsNew, input.IsHot)); + return Process(new TValue(time, GetLastValid(), isNew, this.IsHot)); } this.Value = Calculation(); - return Process(new TValue(Time: Input.Time, Value: this.Value, IsNew: Input.IsNew, IsHot: this.IsHot)); + return Process(new TValue(Time: time, Value: this.Value, IsNew: isNew, IsHot: this.IsHot)); + } + + /// + /// Handles error calculations for inputs with two values. + /// + /// The first input value to check. + /// The second input value to check. + /// The timestamp of the input. + /// Indicates if the input is new. + /// A TValue object with the calculated or last valid value. + /// + /// This method checks for NaN or infinity in both input values. If any invalid value is detected, + /// it returns the last valid value. Otherwise, it proceeds with the calculation. + /// + protected virtual TValue HandleErrorCalculations(double value1, double value2, DateTime time, bool isNew) + { + if (double.IsNaN(value1) || double.IsInfinity(value1) || + double.IsNaN(value2) || double.IsInfinity(value2)) + { + return Process(new TValue(time, GetLastValid(), isNew, this.IsHot)); + } + this.Value = Calculation(); + return Process(new TValue(Time: time, Value: this.Value, IsNew: isNew, IsHot: this.IsHot)); } /// diff --git a/lib/core/tvalue.cs b/lib/core/tvalue.cs index 26bdc1c0..ff7decb9 100644 --- a/lib/core/tvalue.cs +++ b/lib/core/tvalue.cs @@ -52,12 +52,12 @@ public class TSeries : List var pubEvent = source.GetType().GetEvent("Pub"); if (pubEvent != null) { - /* + var nameProperty = source.GetType().GetProperty("Name"); if (nameProperty != null) { Name = nameProperty.GetValue(nameProperty)?.ToString()!; } - */ + pubEvent.AddEventHandler(source, new ValueSignal(Sub)); } } diff --git a/lib/volatility/Atr.cs b/lib/volatility/Atr.cs index 35a67a15..7f3993dd 100644 --- a/lib/volatility/Atr.cs +++ b/lib/volatility/Atr.cs @@ -8,7 +8,7 @@ namespace QuanTAlib; /// of the true range. The true range is the greatest of: current high - current low, /// absolute value of current high - previous close, or absolute value of current low - previous close. /// -public class Atr : AbstractBarBase { +public class Atr : AbstractBase { private readonly Ema _ma; private double _prevClose, _p_prevClose; @@ -72,22 +72,22 @@ public class Atr : AbstractBarBase { /// as the true range. /// protected override double Calculation() { - ManageState(Input.IsNew); + ManageState(BarInput.IsNew); double trueRange = Math.Max( Math.Max( - Input.High - Input.Low, - Math.Abs(Input.High - _prevClose) + BarInput.High - BarInput.Low, + Math.Abs(BarInput.High - _prevClose) ), - Math.Abs(Input.Low - _prevClose) + Math.Abs(BarInput.Low - _prevClose) ); if (_index < 2) { - trueRange = Input.High - Input.Low; + trueRange = BarInput.High - BarInput.Low; } TValue emaTrueRange = _ma.Calc(new TValue(Input.Time, trueRange, Input.IsNew)); IsHot = _ma.IsHot; - _prevClose = Input.Close; + _prevClose = BarInput.Close; return emaTrueRange.Value; } diff --git a/notebooks/means.dib b/notebooks/means.dib index 16530f57..5e4455f2 100644 --- a/notebooks/means.dib +++ b/notebooks/means.dib @@ -10,15 +10,19 @@ QuanTAlib.Formatters.Initialize(); #!csharp -Sma ma1 = new(6); -Gmean ma2 = new (6); -Hmean ma3 = new (6); +TSeries input = new(); +Sma ma1 = new (6); +Sma ma2 = new (input, 6); -double[] input = new[]{1.0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,12,13,14,15,16,17,18,19,20}; -for (int i=0; i atr!; + protected override AbstractBase QuanTAlib => atr!; public override string ShortName => $"ATR {Period}"; public AtrIndicator() { diff --git a/quantower/_IndicatorBarBase.cs b/quantower/_IndicatorBarBase.cs index a5fae449..47bbcc1b 100644 --- a/quantower/_IndicatorBarBase.cs +++ b/quantower/_IndicatorBarBase.cs @@ -19,7 +19,7 @@ public abstract class IndicatorBarBase : Indicator, IWatchlistIndicator // LineSeries.LineSeries(string, Color, int, LineStyle)' protected LineSeries? Series; - protected abstract AbstractBarBase QuanTAlib { get; } + protected abstract AbstractBase QuanTAlib { get; } int IWatchlistIndicator.MinHistoryDepths => 0; @@ -80,7 +80,7 @@ public abstract class IndicatorBarBase : Indicator, IWatchlistIndicator int barX = (int)converter.GetChartX(Time(i)); int barY = (int)converter.GetChartY(Series![i]); int halfBarWidth = CurrentChart.BarsWidth / 2; - Point point = new Point(barX + halfBarWidth, barY); + Point point = new(barX + halfBarWidth, barY); allPoints.Add(point); } @@ -94,22 +94,21 @@ public abstract class IndicatorBarBase : Indicator, IWatchlistIndicator { if (allPoints.Count < 2) { return; } - using (Pen defaultPen = new(Series!.Color, Series.Width) { DashStyle = ConvertLineStyleToDashStyle(Series.Style) }) - using (Pen coldPen = new(Series!.Color, Series.Width) { DashStyle = DashStyle.Dot }) - { - // Draw the hot part - if (hotCount > 0) - { - var hotPoints = allPoints.Take(Math.Min(hotCount + 1, allPoints.Count)).ToArray(); - gr.DrawCurve(defaultPen, hotPoints, 0, hotPoints.Length - 1, (float)0.1); - } + using Pen defaultPen = new(Series!.Color, Series.Width) { DashStyle = ConvertLineStyleToDashStyle(Series.Style) }; + using Pen coldPen = new(Series!.Color, Series.Width) { DashStyle = DashStyle.Dot }; - // Draw the cold part - if (ShowColdValues && hotCount < allPoints.Count) - { - var coldPoints = allPoints.Skip(Math.Max(0, hotCount)).ToArray(); - gr.DrawCurve(coldPen, coldPoints, 0, coldPoints.Length - 1, (float)0.1); - } + // Draw the hot part + if (hotCount > 0) + { + var hotPoints = allPoints.Take(Math.Min(hotCount + 1, allPoints.Count)).ToArray(); + gr.DrawCurve(defaultPen, hotPoints, 0, hotPoints.Length - 1, (float)0.1); + } + + // Draw the cold part + if (ShowColdValues && hotCount < allPoints.Count) + { + var coldPoints = allPoints.Skip(Math.Max(0, hotCount)).ToArray(); + gr.DrawCurve(coldPen, coldPoints, 0, coldPoints.Length - 1, (float)0.1); } } private static DashStyle ConvertLineStyleToDashStyle(LineStyle lineStyle) @@ -125,9 +124,9 @@ public abstract class IndicatorBarBase : Indicator, IWatchlistIndicator } protected static void DrawText(Graphics gr, string text, Rectangle clientRect) { - Font font = new Font("Inter", 8); + Font font = new("Inter", 8); SizeF textSize = gr.MeasureString(text, font); - RectangleF textRect = new RectangleF(clientRect.Left + 5, + RectangleF textRect = new(clientRect.Left + 5, clientRect.Bottom - textSize.Height - 10, textSize.Width + 10, textSize.Height + 10); gr.FillRectangle(SystemBrushes.ControlDarkDark, textRect);