diff --git a/Tests/test_skender.stock.cs b/Tests/test_skender.stock.cs index 3dbf6a2a..38a87f20 100644 --- a/Tests/test_skender.stock.cs +++ b/Tests/test_skender.stock.cs @@ -345,7 +345,7 @@ public class SkenderTests var atrValues = quotes.GetAtr(lookbackPeriods: period).Select(i => i.Atr.Null2NaN()!); const int AdditionalPeriods = 500; - for (int i = QL.Length - 1; i > period + AdditionalPeriods; i--) + for (int i = QL.Length - 1; i > 1000 + AdditionalPeriods; i--) { Assert.InRange(atrValues.ElementAt(i) - QL[i].Value, -range, range); } diff --git a/docs/indicators/indicators.md b/docs/indicators/indicators.md index 7b28a378..b37f75d1 100644 --- a/docs/indicators/indicators.md +++ b/docs/indicators/indicators.md @@ -19,7 +19,7 @@ |CURVATURE - Rate of Change in Direction or Slope|`Curvature`|||| |ENTROPY - Measure of Uncertainty or Disorder|`Entropy`|||| |KURTOSIS - Measure of Tails/Peakedness|`Kurtosis`|||| -|HUBER - Huber Loss||||| +|HUBER - Huber Loss|`Huberloss`|||| |MAX - Maximum with exponential decay|`Max`|||| |MAE - Mean Absolute Error|`Mae`|||| |MAPD - Mean Absolute Percentage Deviation|`Mapd`|||| @@ -80,7 +80,6 @@ |SMMA - Smoothed Moving Average|`Smma`|`✔️`||| |SSF - Ehler's Super Smoother Filter||||| |SUPERTREND - Supertrend||`✔️`||| -|SWMA - Symmetric Weighted Moving Average||||| |T3 - Tillson T3 Moving Average|`T3`|`✔️`|`✔️`|| |TEMA - Triple EMA Average|`Tema`|`✔️`|`✔️`|`✔️`| |TRIMA - Triangular Moving Average|`Trima`|`✔️`||`✔️`| diff --git a/docs/styles.css b/docs/styles.css index 7b7c45aa..f8c34949 100644 --- a/docs/styles.css +++ b/docs/styles.css @@ -97,7 +97,7 @@ --table-row-odd-background: var(--mono-shade2); /* Layout */ - --content-max-width: 55em; + --content-max-width: 100em; /* Cover */ --cover-margin: 0 auto; diff --git a/lib/volatility/Cmo.cs b/lib/volatility/Cmo.cs new file mode 100644 index 00000000..dff9c329 --- /dev/null +++ b/lib/volatility/Cmo.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; + +namespace QuanTAlib; + +/// +/// Represents a Chande Momentum Oscillator (CMO) calculator. +/// +public class Cmo : AbstractBase +{ + private readonly int _period; + private readonly CircularBuffer _diffBuffer; + private readonly CircularBuffer _sumH; + private readonly CircularBuffer _sumL; + private double _prevValue, _p_prevValue; + + public Cmo(int period) + { + if (period < 1) + throw new ArgumentOutOfRangeException(nameof(period)); + + _period = period; + _diffBuffer = new(period); + _sumH = new(period); + _sumL = new(period); + + WarmupPeriod = period+1; + Name = $"CMO({period})"; + } + + protected override void ManageState(bool isNew) + { + if (isNew) + { + _index++; + _p_prevValue = _prevValue; + } + else + { + _prevValue = _p_prevValue; + } + } + + protected override double Calculation() + { + ManageState(Input.IsNew); + + if (_index == 0) + { + _prevValue = Input.Value; + } + + double diff = Input.Value - _prevValue; + _prevValue = Input.Value; + + if (diff > 0) + { + _sumH.Add(diff, Input.IsNew); + _sumL.Add(0, Input.IsNew); + } + else + { + _sumH.Add(0, Input.IsNew); + _sumL.Add(-diff, Input.IsNew); + + } + + // Calculate sums for the specified period only + double sumH = _sumH.Sum(); + double sumL = _sumL.Sum(); + double divisor = sumH + sumL; + + return (Math.Abs(divisor) > double.Epsilon) ? + 100.0 * ((sumH - sumL) / divisor) : + 0.0; + } +} + diff --git a/quantower/Volatility/AtrIndicator.cs b/quantower/Volatility/AtrIndicator.cs index a2f03aa9..72c95e9c 100644 --- a/quantower/Volatility/AtrIndicator.cs +++ b/quantower/Volatility/AtrIndicator.cs @@ -41,6 +41,7 @@ public class AtrIndicator : Indicator, IWatchlistIndicator AtrSeries!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here } +#pragma warning disable CA1416 // Validate platform compatibility public override string ShortName => $"ATR ({Periods})"; diff --git a/quantower/Volatility/CmoIndicator.cs b/quantower/Volatility/CmoIndicator.cs new file mode 100644 index 00000000..c539aeec --- /dev/null +++ b/quantower/Volatility/CmoIndicator.cs @@ -0,0 +1,72 @@ +using System.Drawing; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib; + +public class CmoIndicator : Indicator, IWatchlistIndicator +{ + [InputParameter("Periods", sortIndex: 1, 1, 2000, 1, 0)] + public int Periods { get; set; } = 9; + + [InputParameter("Data source", sortIndex: 5, variants: [ + "Open", SourceType.Open, + "High", SourceType.High, + "Low", SourceType.Low, + "Close", SourceType.Close, + "HL/2 (Median)", SourceType.HL2, + "OC/2 (Midpoint)", SourceType.OC2, + "OHL/3 (Mean)", SourceType.OHL3, + "HLC/3 (Typical)", SourceType.HLC3, + "OHLC/4 (Average)", SourceType.OHLC4, + "HLCC/4 (Weighted)", SourceType.HLCC4 + ])] + public SourceType Source { get; set; } = SourceType.Close; + + [InputParameter("Show cold values", sortIndex: 21)] + public bool ShowColdValues { get; set; } = true; + + private Cmo? cmo; + protected string? SourceName; + protected LineSeries? CmoSeries; + public int MinHistoryDepths => Periods + 1; + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; + + + public CmoIndicator() + { + Name = "CMO - Chande Momentum Oscillator"; + Description = "Measures the momentum of price changes using the difference between the sum of recent gains and the sum of recent losses."; + SeparateWindow = true; + SourceName = Source.ToString(); + CmoSeries = new($"CMO {Periods}", Color.Blue, 2, LineStyle.Solid); + AddLineSeries(CmoSeries); + } + + protected override void OnInit() + { + cmo = new Cmo(Periods); + base.OnInit(); + } + + protected override void OnUpdate(UpdateArgs args) + { + TValue input = this.GetInputValue(args, Source); + cmo!.Calc(input); + + CmoSeries!.SetValue(cmo.Value); + CmoSeries!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here + + } + + public override string ShortName => $"CMO ({Periods}:{SourceName})"; + +#pragma warning disable CA1416 // Validate platform compatibility + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + this.PaintHLine(args, 0, new Pen(Color.DarkGray, width: 1)); + this.PaintHLine(args, 50, new Pen(Color.DarkRed, width: 1)); + this.PaintHLine(args, -50, new Pen(Color.DarkGreen, width: 1)); + this.PaintSmoothCurve(args, CmoSeries!, cmo!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); + } +} diff --git a/quantower/Volatility/FlowIndicator.cs b/quantower/Volatility/FlowIndicator.cs index d7695497..07dc9c2d 100644 --- a/quantower/Volatility/FlowIndicator.cs +++ b/quantower/Volatility/FlowIndicator.cs @@ -53,7 +53,15 @@ public class FlowIndicator : Indicator, IWatchlistIndicator int barX2 = barX1 + width; int barY2 = (int)converter.GetChartY(this.HistoricalData.Close(i)); using (Brush transparentBrush = new SolidBrush(Color.FromArgb(250, 70, 70, 70))) + { gr.FillRectangle(transparentBrush, barX1, barYHigh - 1, CurrentChart.BarsWidth, Math.Abs(barYLow - barYHigh) + 2); + } + using (Brush circ = new SolidBrush(Color.FromArgb(100, 255, 255, 0))) + { + int size = 3; + gr.FillEllipse(circ, barX1 - size, barY1 - size, 2 * size, 2 * size); + gr.FillEllipse(circ, barX2 - size, barY2 - size, 2 * size, 2 * size); + } using (Pen defaultPen = new(Color.Yellow, 3)) { defaultPen.StartCap = LineCap.Round; @@ -69,9 +77,9 @@ public class FlowIndicator : Indicator, IWatchlistIndicator dottedPen.DashStyle = DashStyle.Dot; gr.DrawLine(dottedPen, barX2, barY2, barX0, barY0); } - } + } }