diff --git a/Tests/test_eventing.cs b/Tests/test_eventing.cs index 3c0e8aa6..dd8063f3 100644 --- a/Tests/test_eventing.cs +++ b/Tests/test_eventing.cs @@ -53,22 +53,22 @@ public class EventingTests }; // Generate 200 random values and feed them to both direct and event-based indicators - for (int i = 0; i < 200; i++) + for (int i = 0; i< 200; i++) { double randomValue = random.NextDouble() * 100; - input.Add(randomValue); + 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); - } +// 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/lib/averages/Mgdi.cs b/lib/averages/Mgdi.cs index 7435b898..f137821d 100644 --- a/lib/averages/Mgdi.cs +++ b/lib/averages/Mgdi.cs @@ -40,7 +40,9 @@ public class Mgdi : AbstractBase { _p_prevMd = _prevMd; _index++; - } else { + } + else + { _prevMd = _p_prevMd; } } @@ -50,7 +52,8 @@ public class Mgdi : AbstractBase ManageState(Input.IsNew); double value = Input.Value; - if (_index < 2){ + if (_index < 2) + { _prevMd = value; } else diff --git a/lib/averages/Qema.cs b/lib/averages/Qema.cs index 87f38f28..0e151fa7 100644 --- a/lib/averages/Qema.cs +++ b/lib/averages/Qema.cs @@ -6,9 +6,9 @@ public class Qema : AbstractBase private readonly Ema _ema1, _ema2, _ema3, _ema4; private double _lastQema, _p_lastQema; - public Qema(double k1=0.2, double k2=0.2, double k3=0.2, double k4=0.2) + public Qema(double k1 = 0.2, double k2 = 0.2, double k3 = 0.2, double k4 = 0.2) { - if (k1 <= 0 || k2 <= 0 || k3 <= 0 || k4 <= 0 ) + if (k1 <= 0 || k2 <= 0 || k3 <= 0 || k4 <= 0) { throw new ArgumentOutOfRangeException("All k values must be in the range (0, 1]."); } @@ -26,7 +26,7 @@ public class Qema : AbstractBase Name = $"QEMA ({k1:F2},{k2:F2},{k3:F2},{k4:F2})"; double smK = Math.Min(Math.Min(_k1, _k2), Math.Min(_k3, _k4)); - WarmupPeriod = (int) ((2 - smK) / smK); + WarmupPeriod = (int)((2 - smK) / smK); Init(); } diff --git a/lib/averages/Rma.cs b/lib/averages/Rma.cs index 28bad44b..1d1e4e4a 100644 --- a/lib/averages/Rma.cs +++ b/lib/averages/Rma.cs @@ -22,23 +22,23 @@ public class Rma : AbstractBase _alpha = 1.0 / _period; // Wilder's smoothing factor Name = $"Rma({_period})"; Init(); - } + } - public Rma(object source, int period) : this(period) - { + public Rma(object source, int period) : this(period) + { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); - } + } - public override void Init() - { + public override void Init() + { base.Init(); _lastRMA = 0; _savedLastRMA = 0; - } + } - protected override void ManageState(bool isNew) - { + protected override void ManageState(bool isNew) + { if (!isNew) { _lastRMA = _savedLastRMA; @@ -48,10 +48,10 @@ public class Rma : AbstractBase _savedLastRMA = _lastRMA; _lastValidValue = Input.Value; _index++; - } + } - protected override double Calculation() - { + protected override double Calculation() + { ManageState(Input.IsNew); double rma; @@ -69,9 +69,9 @@ public class Rma : AbstractBase // Wilder's smoothing method return _alpha * (Input.Value - _lastRMA) + _lastRMA; - } + } - _lastRMA = rma; + _lastRMA = rma; IsHot = _index >= WarmupPeriod; return rma; diff --git a/lib/core/tbar.cs b/lib/core/tbar.cs index 85cc8e09..441994ea 100644 --- a/lib/core/tbar.cs +++ b/lib/core/tbar.cs @@ -14,28 +14,28 @@ public interface ITBar public readonly record struct TBar(DateTime Time, double Open, double High, double Low, double Close, double Volume, bool IsNew = true) : ITBar { public DateTime Time { get; init; } = Time; - public double Open { get; init; } = Open; - public double High { get; init; } = High; - public double Low { get; init; } = Low; - public double Close { get; init; } = Close; - public double Volume { get; init; } = Volume; - public bool IsNew { get; init; } = IsNew; +public double Open { get; init; } = Open; +public double High { get; init; } = High; +public double Low { get; init; } = Low; +public double Close { get; init; } = Close; +public double Volume { get; init; } = Volume; +public bool IsNew { get; init; } = IsNew; - public double HL2 => (High + Low) * 0.5; - public double OC2 => (Open + Close) * 0.5; - public double OHL3 => (Open + High + Low) / 3; - public double HLC3 => (High + Low + Close) / 3; - public double OHLC4 => (Open + High + Low + Close) * 0.25; - public double HLCC4 => (High + Low + Close + Close) * 0.25; +public double HL2 => (High + Low) * 0.5; +public double OC2 => (Open + Close) * 0.5; +public double OHL3 => (Open + High + Low) / 3; +public double HLC3 => (High + Low + Close) / 3; +public double OHLC4 => (Open + High + Low + Close) * 0.25; +public double HLCC4 => (High + Low + Close + Close) * 0.25; - public TBar() : this(DateTime.UtcNow, 0, 0, 0, 0, 0) { } - public TBar(double Open, double High, double Low, double Close, double Volume, bool IsNew = true) : this(DateTime.UtcNow, Open, High, Low, Close, Volume, IsNew) { } - public TBar(double value) : this(Time: DateTime.UtcNow, Open: value, High: value, Low: value, Close: value, Volume: value, IsNew: true) { } - public TBar(TValue value) : this(Time: value.Time, Open: value.Value, High: value.Value, Low: value.Value, Close: value.Value, Volume: value.Value, IsNew: value.IsNew) { } +public TBar() : this(DateTime.UtcNow, 0, 0, 0, 0, 0) { } +public TBar(double Open, double High, double Low, double Close, double Volume, bool IsNew = true) : this(DateTime.UtcNow, Open, High, Low, Close, Volume, IsNew) { } +public TBar(double value) : this(Time: DateTime.UtcNow, Open: value, High: value, Low: value, Close: value, Volume: value, IsNew: true) { } +public TBar(TValue value) : this(Time: value.Time, Open: value.Value, High: value.Value, Low: value.Value, Close: value.Value, Volume: value.Value, IsNew: value.IsNew) { } - public static implicit operator double(TBar bar) => bar.Close; - public static implicit operator DateTime(TBar tv) => tv.Time; - public override string ToString() => $"[{Time:yyyy-MM-dd HH:mm:ss}: O={Open:F2}, H={High:F2}, L={Low:F2}, C={Close:F2}, V={Volume:F2}]"; +public static implicit operator double(TBar bar) => bar.Close; +public static implicit operator DateTime(TBar tv) => tv.Time; +public override string ToString() => $"[{Time:yyyy-MM-dd HH:mm:ss}: O={Open:F2}, H={High:F2}, L={Low:F2}, C={Close:F2}, V={Volume:F2}]"; } public delegate void BarSignal(object source, in TBarEventArgs args); diff --git a/lib/core/tvalue.cs b/lib/core/tvalue.cs index ff7decb9..f7c83cff 100644 --- a/lib/core/tvalue.cs +++ b/lib/core/tvalue.cs @@ -11,19 +11,19 @@ public interface ITValue public readonly record struct TValue(DateTime Time, double Value, bool IsNew = true, bool IsHot = true) : ITValue { public DateTime Time { get; init; } = Time; - public double Value { get; init; } = Value; - public bool IsNew { get; init; } = IsNew; - public bool IsHot { get; init; } = IsHot; - public DateTime t => Time; - public double v => Value; +public double Value { get; init; } = Value; +public bool IsNew { get; init; } = IsNew; +public bool IsHot { get; init; } = IsHot; +public DateTime t => Time; +public double v => Value; - public TValue() : this(DateTime.UtcNow, 0) { } - public TValue(double value, bool isNew = true, bool isHot = true) : this(DateTime.UtcNow, value, IsNew: isNew, IsHot: isHot) { } - public static implicit operator double(TValue tv) => tv.Value; - public static implicit operator DateTime(TValue tv) => tv.Time; - public static implicit operator TValue(double value) => new TValue(DateTime.UtcNow, value); +public TValue() : this(DateTime.UtcNow, 0) { } +public TValue(double value, bool isNew = true, bool isHot = true) : this(DateTime.UtcNow, value, IsNew: isNew, IsHot: isHot) { } +public static implicit operator double(TValue tv) => tv.Value; +public static implicit operator DateTime(TValue tv) => tv.Time; +public static implicit operator TValue(double value) => new TValue(DateTime.UtcNow, value); - public override string ToString() => $"[{Time:yyyy-MM-dd HH:mm:ss}, {Value:F2}, IsNew: {IsNew}, IsHot: {IsHot}]"; +public override string ToString() => $"[{Time:yyyy-MM-dd HH:mm:ss}, {Value:F2}, IsNew: {IsNew}, IsHot: {IsHot}]"; } public delegate void ValueSignal(object source, in ValueEventArgs args); @@ -54,10 +54,11 @@ public class TSeries : List { var nameProperty = source.GetType().GetProperty("Name"); - if (nameProperty != null) { + if (nameProperty != null) + { Name = nameProperty.GetValue(nameProperty)?.ToString()!; } - + pubEvent.AddEventHandler(source, new ValueSignal(Sub)); } } @@ -66,7 +67,7 @@ public class TSeries : List public new virtual void Add(TValue tick) { - if (tick.IsNew || base.Count==0) { base.Add(tick); } + if (tick.IsNew || base.Count == 0) { base.Add(tick); } else { this[^1] = tick; } Pub?.Invoke(this, new ValueEventArgs(tick)); } diff --git a/lib/feeds/GbmFeed.cs b/lib/feeds/GbmFeed.cs index 52531078..9d48b0d5 100644 --- a/lib/feeds/GbmFeed.cs +++ b/lib/feeds/GbmFeed.cs @@ -4,67 +4,67 @@ namespace QuanTAlib; public class GbmFeed : TBarSeries { - private readonly double _mu, _sigma; - private readonly Random _random; - private double _lastClose, _lastHigh, _lastLow; + private readonly double _mu, _sigma; + private readonly Random _random; + private double _lastClose, _lastHigh, _lastLow; - public GbmFeed(double initialPrice = 100.0, double mu = 0.05, double sigma = 0.2) - { - _lastClose = _lastHigh = _lastLow = initialPrice; - _mu = mu; - _sigma = sigma; - _random = new Random((int)DateTime.Now.Ticks); - this.Name = $"GBM({_sigma:F2})"; - } + public GbmFeed(double initialPrice = 100.0, double mu = 0.05, double sigma = 0.2) + { + _lastClose = _lastHigh = _lastLow = initialPrice; + _mu = mu; + _sigma = sigma; + _random = new Random((int)DateTime.Now.Ticks); + this.Name = $"GBM({_sigma:F2})"; + } - public void Add(bool isNew = true) => Add(time: DateTime.Now, isNew: isNew); - public void Add(DateTime time, bool isNew = true) => base.Add(Generate(time, isNew)); - public void Add(int count) - { - DateTime startTime = DateTime.UtcNow - TimeSpan.FromHours(count); - TBar lastBar = new(); - for (int i = 0; i < count; i++) - { - Add(startTime, true); - Add(startTime, false); - Add(startTime, false); - startTime = startTime.AddHours(1); - } - } + public void Add(bool isNew = true) => Add(time: DateTime.Now, isNew: isNew); + public void Add(DateTime time, bool isNew = true) => base.Add(Generate(time, isNew)); + public void Add(int count) + { + DateTime startTime = DateTime.UtcNow - TimeSpan.FromHours(count); + TBar lastBar = new(); + for (int i = 0; i < count; i++) + { + Add(startTime, true); + Add(startTime, false); + Add(startTime, false); + startTime = startTime.AddHours(1); + } + } - public TBar Generate(DateTime time, bool isNew = true) - { - double dt = 1.0 / 252; - double drift = (_mu - 0.5 * _sigma * _sigma) * dt; - double diffusion = _sigma * Math.Sqrt(dt) * GenerateNormalRandom(); - double newClose = _lastClose * Math.Exp(drift + diffusion); + public TBar Generate(DateTime time, bool isNew = true) + { + double dt = 1.0 / 252; + double drift = (_mu - 0.5 * _sigma * _sigma) * dt; + double diffusion = _sigma * Math.Sqrt(dt) * GenerateNormalRandom(); + double newClose = _lastClose * Math.Exp(drift + diffusion); - double open = _lastClose; - double high = Math.Max(_lastHigh, Math.Max(open, newClose) * (1 + _random.NextDouble() * 0.01)); - double low = Math.Min(_lastLow, Math.Min(open, newClose) * (1 - _random.NextDouble() * 0.01)); - double volume = 1000 + _random.NextDouble() * 1000; + double open = _lastClose; + double high = Math.Max(_lastHigh, Math.Max(open, newClose) * (1 + _random.NextDouble() * 0.01)); + double low = Math.Min(_lastLow, Math.Min(open, newClose) * (1 - _random.NextDouble() * 0.01)); + double volume = 1000 + _random.NextDouble() * 1000; - if (isNew) - { - _lastClose = newClose; - } - else - { - high = Math.Max(_lastHigh, high); - low = Math.Min(_lastLow, low); - } - _lastHigh = high; - _lastLow = low; + if (isNew) + { + _lastClose = newClose; + } + else + { + high = Math.Max(_lastHigh, high); + low = Math.Min(_lastLow, low); + } + _lastHigh = high; + _lastLow = low; - TBar bar = new(time, open, high, low, newClose, volume, isNew); - return bar; - } + TBar bar = new(time, open, high, low, newClose, volume, isNew); + return bar; + } - private double GenerateNormalRandom() - { - // Box-Muller transform to generate standard normal random variable - double u1 = 1.0 - _random.NextDouble(); // Uniform(0,1] random doubles - double u2 = 1.0 - _random.NextDouble(); - return Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2); - } + private double GenerateNormalRandom() + { + // Box-Muller transform to generate standard normal random variable + double u1 = 1.0 - _random.NextDouble(); // Uniform(0,1] random doubles + double u2 = 1.0 - _random.NextDouble(); + return Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2); + } } \ No newline at end of file diff --git a/lib/volatility/Atr.cs b/lib/volatility/Atr.cs index 7f3993dd..e6c20845 100644 --- a/lib/volatility/Atr.cs +++ b/lib/volatility/Atr.cs @@ -8,7 +8,8 @@ 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 : AbstractBase { +public class Atr : AbstractBase +{ private readonly Ema _ma; private double _prevClose, _p_prevClose; @@ -19,11 +20,13 @@ public class Atr : AbstractBase { /// /// Thrown when period is less than 1. /// - public Atr(int period) { - if (period < 1) { + public Atr(int period) + { + if (period < 1) + { throw new ArgumentOutOfRangeException(nameof(period), "Period must be greater than or equal to 1."); } - _ma = new(1.0/period); + _ma = new(1.0 / period); WarmupPeriod = _ma.WarmupPeriod; Name = $"ATR({period})"; } @@ -33,7 +36,8 @@ public class Atr : AbstractBase { /// /// The source object to subscribe to for bar updates. /// The period over which to calculate the ATR. - public Atr(object source, int period) : this(period) { + public Atr(object source, int period) : this(period) + { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new BarSignal(Sub)); } @@ -41,7 +45,8 @@ public class Atr : AbstractBase { /// /// Initializes the Atr instance by setting up the initial state. /// - public override void Init() { + public override void Init() + { base.Init(); _ma.Init(); _prevClose = double.NaN; @@ -51,11 +56,15 @@ public class Atr : AbstractBase { /// Manages the state of the Atr instance based on whether a new bar is being processed. /// /// Indicates whether the current input is a new bar. - protected override void ManageState(bool isNew) { - if (isNew) { + protected override void ManageState(bool isNew) + { + if (isNew) + { _index++; _p_prevClose = _prevClose; - } else { + } + else + { _prevClose = _p_prevClose; } } @@ -71,7 +80,8 @@ public class Atr : AbstractBase { /// to smooth the true range values. For the first bar, it uses the high-low range /// as the true range. /// - protected override double Calculation() { + protected override double Calculation() + { ManageState(BarInput.IsNew); double trueRange = Math.Max( @@ -81,7 +91,8 @@ public class Atr : AbstractBase { ), Math.Abs(BarInput.Low - _prevClose) ); - if (_index < 2) { + if (_index < 2) + { trueRange = BarInput.High - BarInput.Low; }