diff --git a/.clinerules/good-indicator.md b/.clinerules/good-indicator.md index c5d83856..3bfd4eb3 100644 --- a/.clinerules/good-indicator.md +++ b/.clinerules/good-indicator.md @@ -11,6 +11,7 @@ This document defines the strict standards for creating high-quality technical i * **Bar Correction:** Support intra-bar updates via the `isNew` parameter. The indicator must be able to rollback the last update and apply a new value for the same timestamp. * **Robustness:** Handle `NaN` and `Infinity` gracefully using last-valid-value substitution. Never propagate invalid values. * **Reactive:** Implement `ITValuePublisher` to support event-driven architectures. +* **Time Handling:** Always use `DateTime.UtcNow` instead of `DateTime.Now` to ensure consistent time handling across timezones. ## 2. File Structure @@ -91,6 +92,7 @@ Each indicator resides in its own directory such as `lib/trends/`, `lib/indicato ### Unit Tests (`[Name].Tests.cs`) * **Framework:** xUnit +* **Data Generation:** Use `GBM` (Geometric Brownian Motion) for generating realistic test data. Avoid using `System.Random` directly. * **Coverage:** * Constructor validation (invalid params). @@ -155,7 +157,7 @@ Template structure: ## 9. Checklist for New Indicators -* [ ] **Source Material:** Sourced algorithm and docs from `mihakralj/pinescript`? +* [ ] **Source Material:** Sourced algorithm and docs from `mihakralj/pinescript` or `mihakralj/quantalib`? * [ ] **File Structure:** Created all 6 required files? * [ ] **Constructor:** Validates inputs? Sets `Name`? * [ ] **Update:** Handles `isNew` correctly? Handles `NaN`? O(1)? diff --git a/lib/trends/_index.md b/lib/trends/_index.md index ab3e3464..c1df7a13 100644 --- a/lib/trends/_index.md +++ b/lib/trends/_index.md @@ -16,7 +16,7 @@ Trend indicators help identify the direction and strength of a market trend. Mov | [CONV](trends/conv/Conv.md) | Convolution Indicator | Applies a custom kernel (weights) to the data window. | | [DEMA](trends/dema/Dema.md) | Double Exponential Moving Average | Reduces lag by placing more weight on recent data than a standard EMA. | | DSMA | Deviation-Scaled MA | | -| DWMA | Double Weighted MA | | +| [DWMA](trends/dwma/Dwma.md) | Double Weighted MA | Applies WMA smoothing twice to reduce noise further. | | ELLIPTIC | Elliptic (Cauer) Filter | | | [EMA](trends/ema/Ema.md) | Exponential Moving Average | Weighted average giving more importance to recent price data. | | EPMA | Endpoint MA | | diff --git a/lib/trends/conv/Conv.Validation.Tests.cs b/lib/trends/conv/Conv.Validation.Tests.cs index 6f1305a4..994be669 100644 --- a/lib/trends/conv/Conv.Validation.Tests.cs +++ b/lib/trends/conv/Conv.Validation.Tests.cs @@ -17,11 +17,11 @@ public class ConvValidationTests var sma = new Sma(period); var conv = new Conv(kernel); - var rnd = new Random(123); + var gbm = new GBM(startPrice: 100, seed: 123); for (int i = 0; i < 1000; i++) { - double price = rnd.NextDouble() * 100; - var tValue = new TValue(DateTime.UtcNow, price); + var bar = gbm.Next(); + var tValue = bar.C; var smaVal = sma.Update(tValue); var convVal = conv.Update(tValue); @@ -48,11 +48,11 @@ public class ConvValidationTests var wma = new Wma(period); var conv = new Conv(kernel); - var rnd = new Random(123); + var gbm = new GBM(startPrice: 100, seed: 123); for (int i = 0; i < 1000; i++) { - double price = rnd.NextDouble() * 100; - var tValue = new TValue(DateTime.UtcNow, price); + var bar = gbm.Next(); + var tValue = bar.C; var wmaVal = wma.Update(tValue); var convVal = conv.Update(tValue); @@ -105,11 +105,11 @@ public class ConvValidationTests var trima = new Trima(period); var conv = new Conv(kernel); - var rnd = new Random(123); + var gbm = new GBM(startPrice: 100, seed: 123); for (int i = 0; i < 1000; i++) { - double price = rnd.NextDouble() * 100; - var tValue = new TValue(DateTime.UtcNow, price); + var bar = gbm.Next(); + var tValue = bar.C; var trimaVal = trima.Update(tValue); var convVal = conv.Update(tValue); diff --git a/lib/trends/dema/Dema.Tests.cs b/lib/trends/dema/Dema.Tests.cs index 00b519c4..58da6a14 100644 --- a/lib/trends/dema/Dema.Tests.cs +++ b/lib/trends/dema/Dema.Tests.cs @@ -79,7 +79,7 @@ public class DemaTests // Assert for (int i = 0; i < count; i++) { - var val = demaObj.Update(new TValue(DateTime.Now, source[i])); + var val = demaObj.Update(new TValue(DateTime.UtcNow, source[i])); Assert.Equal(val.Value, output[i], 1e-9); } } @@ -156,7 +156,7 @@ public class DemaTests // Assert for (int i = 0; i < count; i++) { - var val = demaObj.Update(new TValue(DateTime.Now, source[i])); + var val = demaObj.Update(new TValue(DateTime.UtcNow, source[i])); Assert.Equal(val.Value, output[i], 1e-9); } } diff --git a/lib/trends/dwma/Dwma.Quantower.Tests.cs b/lib/trends/dwma/Dwma.Quantower.Tests.cs new file mode 100644 index 00000000..b6666183 --- /dev/null +++ b/lib/trends/dwma/Dwma.Quantower.Tests.cs @@ -0,0 +1,78 @@ +using Xunit; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib.Tests; + +public class DwmaIndicatorTests +{ + [Fact] + public void DwmaIndicator_Constructor_SetsDefaults() + { + var indicator = new DwmaIndicator(); + + Assert.Equal(10, indicator.Period); + Assert.Equal(SourceType.Close, indicator.Source); + Assert.True(indicator.ShowColdValues); + Assert.Equal("DWMA - Double Weighted Moving Average", indicator.Name); + Assert.False(indicator.SeparateWindow); + Assert.True(indicator.OnBackGround); + } + + [Fact] + public void DwmaIndicator_MinHistoryDepths_EqualsTwoTimesPeriod() + { + var indicator = new DwmaIndicator { Period = 20 }; + + Assert.Equal(40, indicator.MinHistoryDepths); + Assert.Equal(40, ((IWatchlistIndicator)indicator).MinHistoryDepths); + } + + [Fact] + public void DwmaIndicator_ShortName_IncludesPeriodAndSource() + { + var indicator = new DwmaIndicator { Period = 15 }; + + Assert.Contains("DWMA", indicator.ShortName); + Assert.Contains("15", indicator.ShortName); + } + + [Fact] + public void DwmaIndicator_SourceCodeLink_IsValid() + { + var indicator = new DwmaIndicator(); + + Assert.Contains("github.com", indicator.SourceCodeLink); + Assert.Contains("Dwma.Quantower.cs", indicator.SourceCodeLink); + } + + [Fact] + public void DwmaIndicator_Initialize_CreatesInternalDwma() + { + var indicator = new DwmaIndicator { Period = 10 }; + + // Initialize should not throw + indicator.Initialize(); + + // After init, line series should exist + Assert.Single(indicator.LinesSeries); + } + + [Fact] + public void DwmaIndicator_ProcessUpdate_HistoricalBar_ComputesValue() + { + var indicator = new DwmaIndicator { Period = 3 }; + indicator.Initialize(); + + // Add historical data + var now = DateTime.UtcNow; + indicator.HistoricalData.AddBar(now, 100, 105, 95, 102); + + // Process update + var args = new UpdateArgs(UpdateReason.HistoricalBar); + indicator.ProcessUpdate(args); + + // Line series should have a value + Assert.Equal(1, indicator.LinesSeries[0].Count); + Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0))); + } +} diff --git a/lib/trends/dwma/Dwma.Quantower.cs b/lib/trends/dwma/Dwma.Quantower.cs new file mode 100644 index 00000000..f5d03dba --- /dev/null +++ b/lib/trends/dwma/Dwma.Quantower.cs @@ -0,0 +1,63 @@ +using System.Drawing; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib; + +public class DwmaIndicator : Indicator, IWatchlistIndicator +{ + [InputParameter("Period", sortIndex: 1, 1, 1000, 1, 0)] + public int Period { get; set; } = 10; + + [IndicatorExtensions.DataSourceInput] + public SourceType Source { get; set; } = SourceType.Close; + + [InputParameter("Show cold values", sortIndex: 21)] + public bool ShowColdValues { get; set; } = true; + + private Dwma? ma; + private int _warmupBarIndex = -1; + protected LineSeries? Series; + protected string? SourceName; + + public int MinHistoryDepths => Period * 2; // DWMA needs roughly 2x period to warm up + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; + + public override string ShortName => $"DWMA {Period}:{SourceName}"; + public override string SourceCodeLink => "https://github.com/mihakralj/QuanTAlib/blob/main/lib/trends/dwma/Dwma.Quantower.cs"; + + public DwmaIndicator() + { + OnBackGround = true; + SeparateWindow = false; + SourceName = Source.ToString(); + Name = "DWMA - Double Weighted Moving Average"; + Description = "Double Weighted Moving Average"; + Series = new(name: $"DWMA {Period}", color: IndicatorExtensions.Averages, width: 2, style: LineStyle.Solid); + AddLineSeries(Series); + } + + protected override void OnInit() + { + ma = new Dwma(Period); + _warmupBarIndex = -1; + SourceName = Source.ToString(); + base.OnInit(); + } + + protected override void OnUpdate(UpdateArgs args) + { + TValue input = this.GetInputValue(args, Source); + bool isNew = args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar; + TValue result = ma!.Update(input, isNew); + if (_warmupBarIndex < 0 && ma!.IsHot) + _warmupBarIndex = Count; + Series!.SetValue(result.Value); + Series!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here + } + + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + this.PaintSmoothCurve(args, Series!, _warmupBarIndex, showColdValues: ShowColdValues, tension: 0.2); + } +} diff --git a/lib/trends/dwma/Dwma.Tests.cs b/lib/trends/dwma/Dwma.Tests.cs new file mode 100644 index 00000000..3acc5825 --- /dev/null +++ b/lib/trends/dwma/Dwma.Tests.cs @@ -0,0 +1,102 @@ +using System; +using Xunit; + +namespace QuanTAlib; + +public class DwmaTests +{ + [Fact] + public void Constructor_InvalidPeriod_ThrowsArgumentException() + { + Assert.Throws(() => new Dwma(0)); + Assert.Throws(() => new Dwma(-1)); + } + + [Fact] + public void Update_ValidInput_CalculatesCorrectly() + { + // DWMA(3) of [1, 2, 3, 4, 5] + // WMA(3) of [1, 2, 3, 4, 5] + // 1: 1 + // 2: (1*1 + 2*2) / 3 = 5/3 = 1.666... + // 3: (1*1 + 2*2 + 3*3) / 6 = 14/6 = 2.333... + // 4: (1*2 + 2*3 + 3*4) / 6 = 20/6 = 3.333... + // 5: (1*3 + 2*4 + 3*5) / 6 = 26/6 = 4.333... + + // WMA(3) results: [1, 1.666, 2.333, 3.333, 4.333] + + // DWMA(3) = WMA(3) of [1, 1.666, 2.333, 3.333, 4.333] + // 1: 1 + // 2: (1*1 + 2*1.666) / 3 = 4.333/3 = 1.444... + // 3: (1*1 + 2*1.666 + 3*2.333) / 6 = (1 + 3.333 + 7) / 6 = 11.333/6 = 1.888... + + var dwma = new Dwma(3); + + var v1 = dwma.Update(new TValue(DateTime.UtcNow, 1)).Value; + var v2 = dwma.Update(new TValue(DateTime.UtcNow, 2)).Value; + var v3 = dwma.Update(new TValue(DateTime.UtcNow, 3)).Value; + + Assert.Equal(1.0, v1, 6); + Assert.Equal(1.444444, v2, 5); + Assert.Equal(1.888888, v3, 5); + } + + [Fact] + public void Update_IsNewFalse_CorrectsValue() + { + var dwma = new Dwma(3); + + dwma.Update(new TValue(DateTime.UtcNow, 1)); + dwma.Update(new TValue(DateTime.UtcNow, 2)); + + // Update with 3, then correct to 4 + var v3 = dwma.Update(new TValue(DateTime.UtcNow, 3), isNew: true).Value; + var v3_corrected = dwma.Update(new TValue(DateTime.UtcNow, 4), isNew: false).Value; + + // Manual calc for sequence [1, 2, 4] + // WMA(3): + // 1: 1 + // 2: 1.666 + // 4: (1*1 + 2*2 + 3*4) / 6 = 17/6 = 2.8333 + + // DWMA(3) of [1, 1.666, 2.8333] + // 3: (1*1 + 2*1.666 + 3*2.8333) / 6 = (1 + 3.333 + 8.5) / 6 = 12.833/6 = 2.1388 + + Assert.Equal(1.888888, v3, 5); // From previous test + Assert.Equal(2.138888, v3_corrected, 5); + } + + [Fact] + public void Reset_ClearsState() + { + var dwma = new Dwma(3); + dwma.Update(new TValue(DateTime.UtcNow, 1)); + dwma.Update(new TValue(DateTime.UtcNow, 2)); + + dwma.Reset(); + + Assert.False(dwma.IsHot); + var v1 = dwma.Update(new TValue(DateTime.UtcNow, 1)).Value; + Assert.Equal(1.0, v1); + } + + [Fact] + public void StaticCalculate_MatchesInstance() + { + int period = 10; + int count = 100; + var source = new TSeries(); + var dwma = new Dwma(period); + + for (int i = 0; i < count; i++) + { + source.Add(new TValue(DateTime.UtcNow.AddMinutes(i), i)); + dwma.Update(source.Last); + } + + var staticResult = Dwma.Calculate(source, period); + + Assert.Equal(source.Count, staticResult.Count); + Assert.Equal(dwma.Last.Value, staticResult.Last.Value, 8); + } +} diff --git a/lib/trends/dwma/Dwma.Validation.Tests.cs b/lib/trends/dwma/Dwma.Validation.Tests.cs new file mode 100644 index 00000000..e8706386 --- /dev/null +++ b/lib/trends/dwma/Dwma.Validation.Tests.cs @@ -0,0 +1,41 @@ +using System; +using Xunit; + +namespace QuanTAlib; + +public class DwmaValidationTests +{ + [Fact] + public void Validate_Against_DoubleWma() + { + // DWMA should be exactly WMA(WMA(source, period), period) + + int period = 10; + int count = 1000; + var source = new TSeries(); + var rnd = new Random(42); + + for (int i = 0; i < count; i++) + { + source.Add(new TValue(DateTime.UtcNow.AddMinutes(i), rnd.NextDouble() * 100)); + } + + var dwma = new Dwma(period); + var wma1 = new Wma(period); + var wma2 = new Wma(period); + + for (int i = 0; i < count; i++) + { + var val = source[i]; + + // Calculate DWMA + var dwmaVal = dwma.Update(val); + + // Calculate WMA(WMA) manually + var wma1Val = wma1.Update(val); + var wma2Val = wma2.Update(wma1Val); + + Assert.Equal(wma2Val.Value, dwmaVal.Value, 10); + } + } +} diff --git a/lib/trends/dwma/Dwma.cs b/lib/trends/dwma/Dwma.cs new file mode 100644 index 00000000..b193b025 --- /dev/null +++ b/lib/trends/dwma/Dwma.cs @@ -0,0 +1,139 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace QuanTAlib; + +/// +/// DWMA: Double Weighted Moving Average +/// +/// +/// DWMA applies a Weighted Moving Average (WMA) twice. +/// It provides a smoother curve than a standard WMA but with slightly more lag. +/// +/// Formula: +/// DWMA = WMA(WMA(source, period), period) +/// +[SkipLocalsInit] +public sealed class Dwma : ITValuePublisher +{ + private readonly int _period; + private readonly Wma _wma1; + private readonly Wma _wma2; + + /// + /// Display name for the indicator. + /// + public string Name { get; } + + /// + /// Current DWMA value. + /// + public TValue Last { get; private set; } + + /// + /// True if the indicator has enough data to produce valid results. + /// + public bool IsHot => _wma1.IsHot && _wma2.IsHot; + + public event Action? Pub; + + /// + /// Creates DWMA with specified period. + /// + /// Window size (must be > 0) + public Dwma(int period) + { + if (period <= 0) + throw new ArgumentException("Period must be greater than 0", nameof(period)); + + _period = period; + _wma1 = new Wma(period); + _wma2 = new Wma(period); + Name = $"Dwma({period})"; + } + + public Dwma(ITValuePublisher source, int period) : this(period) + { + source.Pub += (item) => Update(item); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TValue Update(TValue input, bool isNew = true) + { + TValue wma1Result = _wma1.Update(input, isNew); + Last = _wma2.Update(wma1Result, isNew); + Pub?.Invoke(Last); + return Last; + } + + public TSeries Update(TSeries source) + { + if (source.Count == 0) return new TSeries(); + + int len = source.Count; + var t = new List(len); + var v = new List(len); + CollectionsMarshal.SetCount(t, len); + CollectionsMarshal.SetCount(v, len); + + var tSpan = CollectionsMarshal.AsSpan(t); + var vSpan = CollectionsMarshal.AsSpan(v); + + source.Times.CopyTo(tSpan); + Calculate(source.Values, vSpan, _period); + + // Restore state + // We need to replay the last part to restore the internal WMAs state + // Since DWMA is WMA(WMA), the effective lookback is roughly 2*Period + // But to be safe and simple, we can just reset and replay the last 2*Period bars. + + _wma1.Reset(); + _wma2.Reset(); + + int warmup = _period * 2; // Approximate warmup needed + int startIndex = Math.Max(0, len - warmup); + + for (int i = startIndex; i < len; i++) + { + Update(new TValue(source.Times[i], source.Values[i])); + } + + return new TSeries(t, v); + } + + public static TSeries Calculate(TSeries source, int period) + { + var dwma = new Dwma(period); + return dwma.Update(source); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Calculate(ReadOnlySpan source, Span output, int period) + { + if (source.Length != output.Length) + throw new ArgumentException("Source and output must have the same length"); + + // We need a temporary buffer for the first WMA pass + // Use stackalloc for small sizes, heap for large + if (source.Length <= 1024) + { + Span temp = stackalloc double[source.Length]; + Wma.Calculate(source, temp, period); + Wma.Calculate(temp, output, period); + } + else + { + double[] temp = new double[source.Length]; + Wma.Calculate(source, temp, period); + Wma.Calculate(temp, output, period); + } + } + + public void Reset() + { + _wma1.Reset(); + _wma2.Reset(); + Last = default; + } +} diff --git a/lib/trends/dwma/Dwma.md b/lib/trends/dwma/Dwma.md new file mode 100644 index 00000000..3beef1cf --- /dev/null +++ b/lib/trends/dwma/Dwma.md @@ -0,0 +1,67 @@ +# DWMA - Double Weighted Moving Average + +DWMA is a moving average that applies the Weighted Moving Average (WMA) twice. It provides a smoother curve than a standard WMA but with slightly more lag. + +## Core Concepts + +* **Double Smoothing:** Applies WMA smoothing twice to reduce noise further. +* **Weighted:** Gives more weight to recent data points, similar to WMA. +* **Recursive Calculation:** Uses the efficient O(1) WMA implementation. + +## Parameters + +| Parameter | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `period` | `int` | - | The lookback period for both WMA passes. | + +## Formula + +$$ +DWMA_t = WMA(WMA(Price, n), n) +$$ + +Where: + +* $WMA$ is the Weighted Moving Average. +* $n$ is the period. + +## C# Implementation + +### Standard Usage + +```csharp +// Create DWMA with period 14 +var dwma = new Dwma(14); + +// Update with new value +var result = dwma.Update(new TValue(DateTime.Now, 123.45)); +Console.WriteLine($"DWMA: {result.Value}"); +``` + +### Span API (High Performance) + +```csharp +// Calculate on a span of data +ReadOnlySpan input = ...; +Span output = new double[input.Length]; + +Dwma.Calculate(input, output, 14); +``` + +### Bar Correction + +```csharp +// Update with a value +dwma.Update(new TValue(time, 100), isNew: true); + +// Correct the last value +dwma.Update(new TValue(time, 101), isNew: false); +``` + +## Interpretation + +DWMA is used similarly to other moving averages to identify trends. Due to the double smoothing, it is less susceptible to whipsaws than WMA but reacts slower to price changes. + +## References + +* [Pine Script Implementation](https://github.com/mihakralj/pinescript/blob/main/indicators/trends_FIR/dwma.md) diff --git a/lib/trends/lsma/Lsma.Tests.cs b/lib/trends/lsma/Lsma.Tests.cs index d8ba64ae..a96b2f5e 100644 --- a/lib/trends/lsma/Lsma.Tests.cs +++ b/lib/trends/lsma/Lsma.Tests.cs @@ -26,7 +26,7 @@ public class LsmaTests public void Update_SingleValue_ReturnsSameValue() { var lsma = new Lsma(14); - var result = lsma.Update(new TValue(DateTime.Now, 100)); + var result = lsma.Update(new TValue(DateTime.UtcNow, 100)); Assert.Equal(100, result.Value); } @@ -39,7 +39,7 @@ public class LsmaTests for (int i = 0; i < period * 2; i++) { - var result = lsma.Update(new TValue(DateTime.Now, i)); + var result = lsma.Update(new TValue(DateTime.UtcNow, i)); if (i >= period) // After warmup { Assert.Equal(i, result.Value, 1e-9); @@ -56,7 +56,7 @@ public class LsmaTests for (int i = 0; i < period * 2; i++) { - var result = lsma.Update(new TValue(DateTime.Now, value)); + var result = lsma.Update(new TValue(DateTime.UtcNow, value)); Assert.Equal(value, result.Value, 1e-9); } } @@ -75,7 +75,7 @@ public class LsmaTests for (int i = 0; i < 20; i++) { double y = 2 * i + 1; - var result = lsma.Update(new TValue(DateTime.Now, y)); + var result = lsma.Update(new TValue(DateTime.UtcNow, y)); if (i >= period) { @@ -93,20 +93,20 @@ public class LsmaTests // Fill buffer for (int i = 0; i < 5; i++) { - lsma.Update(new TValue(DateTime.Now, i)); + lsma.Update(new TValue(DateTime.UtcNow, i)); } // New bar - var result1 = lsma.Update(new TValue(DateTime.Now, 10)); + var result1 = lsma.Update(new TValue(DateTime.UtcNow, 10)); // Update same bar with different value - var result2 = lsma.Update(new TValue(DateTime.Now, 20), isNew: false); + var result2 = lsma.Update(new TValue(DateTime.UtcNow, 20), isNew: false); Assert.NotEqual(result1.Value, result2.Value); // Verify internal state by adding next bar // If state was corrupted, this would fail - var result3 = lsma.Update(new TValue(DateTime.Now, 30)); + var result3 = lsma.Update(new TValue(DateTime.UtcNow, 30)); Assert.True(double.IsFinite(result3.Value)); } @@ -115,9 +115,9 @@ public class LsmaTests { var lsma = new Lsma(5); - lsma.Update(new TValue(DateTime.Now, 1)); - lsma.Update(new TValue(DateTime.Now, 2)); - var result = lsma.Update(new TValue(DateTime.Now, double.NaN)); + lsma.Update(new TValue(DateTime.UtcNow, 1)); + lsma.Update(new TValue(DateTime.UtcNow, 2)); + var result = lsma.Update(new TValue(DateTime.UtcNow, double.NaN)); // Input sequence becomes: 1, 2, 2 (NaN replaced by last valid 2) // Regression on (2,1), (1,2), (0,2) @@ -131,11 +131,12 @@ public class LsmaTests int period = 10; int count = 100; var source = new TSeries(); - var rnd = new Random(42); + var gbm = new GBM(startPrice: 100, seed: 42); for (int i = 0; i < count; i++) { - source.Add(new TValue(DateTime.Now.AddMinutes(i), rnd.NextDouble() * 100)); + var bar = gbm.Next(); + source.Add(bar.C); } var lsma = new Lsma(period); @@ -156,11 +157,12 @@ public class LsmaTests int count = 100; var values = new double[count]; var output = new double[count]; - var rnd = new Random(42); + var gbm = new GBM(startPrice: 100, seed: 42); for (int i = 0; i < count; i++) { - values[i] = rnd.NextDouble() * 100; + var bar = gbm.Next(); + values[i] = bar.Close; } Lsma.Calculate(values, output, period); @@ -168,7 +170,7 @@ public class LsmaTests var lsma = new Lsma(period); for (int i = 0; i < count; i++) { - var result = lsma.Update(new TValue(DateTime.Now, values[i])); + var result = lsma.Update(new TValue(DateTime.UtcNow, values[i])); Assert.Equal(result.Value, output[i], 1e-9); } } @@ -179,7 +181,7 @@ public class LsmaTests var lsma = new Lsma(5); for (int i = 0; i < 10; i++) { - lsma.Update(new TValue(DateTime.Now, i)); + lsma.Update(new TValue(DateTime.UtcNow, i)); } Assert.True(lsma.IsHot); @@ -190,7 +192,7 @@ public class LsmaTests Assert.Equal(0, lsma.Last.Value); // Should behave like new instance - var result = lsma.Update(new TValue(DateTime.Now, 100)); + var result = lsma.Update(new TValue(DateTime.UtcNow, 100)); Assert.Equal(100, result.Value); } }