From efc8e553dbf1b73c23dbff5b641c0e7ed87e6e9b Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Thu, 24 Oct 2024 18:30:59 -0700 Subject: [PATCH] Rsi and Rsx --- docs/indicators/indicators.md | 2 +- lib/averages/Jma.cs | 8 ++-- lib/volatility/Rsi.cs | 62 +++++++++++++++++++++++++ lib/volatility/Rsx.cs | 64 ++++++++++++++++++++++++++ quantower/Averages/MacdIndicator.cs | 4 +- quantower/Volatility/RsiIndicator.cs | 67 ++++++++++++++++++++++++++++ quantower/Volatility/RsxIndicator.cs | 67 ++++++++++++++++++++++++++++ 7 files changed, 267 insertions(+), 7 deletions(-) create mode 100644 lib/volatility/Rsi.cs create mode 100644 lib/volatility/Rsx.cs create mode 100644 quantower/Volatility/RsiIndicator.cs create mode 100644 quantower/Volatility/RsxIndicator.cs diff --git a/docs/indicators/indicators.md b/docs/indicators/indicators.md index 897a417a..4ef85e51 100644 --- a/docs/indicators/indicators.md +++ b/docs/indicators/indicators.md @@ -23,7 +23,7 @@ |KEL - Keltner Channels||GetKeltner||| |NATR - Normalized Average True Range||GetAtr||| |CHN - Price Channel Indicator||||| -|RSI - Relative Strength Index||GetRsi||| +|RSI - Relative Strength Index|`Rsi`|GetRsi||| |SAR - Parabolic Stop and Reverse||GetParabolicSar||| |SRSI - Stochastic RSI||GetStochRsi||| |STARC - Starc Bands||GetStarcBands||| diff --git a/lib/averages/Jma.cs b/lib/averages/Jma.cs index c8e8eba0..0d3777cb 100644 --- a/lib/averages/Jma.cs +++ b/lib/averages/Jma.cs @@ -32,7 +32,7 @@ public class Jma : AbstractBase /// /// Thrown when period is less than 1. /// - public Jma(int period, int phase = 0, double factor = 0.45) + public Jma(int period, int phase = 0, double factor = 0.45, int buffer = 10) { if (period < 1) { @@ -42,7 +42,7 @@ public class Jma : AbstractBase _period = period; _phase = Math.Clamp((phase * 0.01) + 1.5, 0.5, 2.5); - _vsumBuff = new CircularBuffer(10); + _vsumBuff = new CircularBuffer(buffer); _avoltyBuff = new CircularBuffer(65); _beta = factor * (_period - 1) / (factor * (_period - 1) + 2); @@ -56,7 +56,7 @@ public class Jma : AbstractBase /// The source object to subscribe to for value updates. /// The period over which to calculate the Jvolty. /// The phase parameter for the JMA-style calculation. - public Jma(object source, int period, int phase = 0) : this(period, phase) + public Jma(object source, int period, int phase = 0, double factor = 0.45, int buffer = 10) : this(period, phase, factor, buffer) { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); @@ -148,7 +148,7 @@ public class Jma : AbstractBase _prevDet0 = det0; double ma2 = ma1 + _phase * det0; - double det1 = ((ma2 - _prevJma) * (1 - _alpha) * (1 - _alpha) ) + (_alpha * _alpha * _prevDet1); + double det1 = ((ma2 - _prevJma) * (1 - _alpha) * (1 - _alpha)) + (_alpha * _alpha * _prevDet1); _prevDet1 = det1; double jma = _prevJma + det1; _prevJma = jma; diff --git a/lib/volatility/Rsi.cs b/lib/volatility/Rsi.cs new file mode 100644 index 00000000..9252281d --- /dev/null +++ b/lib/volatility/Rsi.cs @@ -0,0 +1,62 @@ +using System; + +namespace QuanTAlib; + +/// +/// Represents a Relative Strength Index (RSI) calculator following Wilder's algorithm. +/// +public class Rsi : AbstractBase +{ + private readonly Rma _avgGain; + private readonly Rma _avgLoss; + private double _prevValue, _p_prevValue; + + public Rsi(int period = 14) + { + if (period < 1) + throw new ArgumentOutOfRangeException(nameof(period)); + _avgGain = new(period, useSma: true); + _avgLoss = new(period, useSma: true); + _index = 0; + WarmupPeriod = period + 1; + Name = $"RSI({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 == 1) + { + _prevValue = Input.Value; + } + + double change = Input.Value - _prevValue; + double gain = Math.Max(change, 0); + double loss = Math.Max(-change, 0); + _prevValue = Input.Value; + + _avgGain.Calc(gain, IsNew: Input.IsNew); + _avgLoss.Calc(loss, IsNew: Input.IsNew); + + double rsi = (_avgLoss.Value > 0) ? 100 - (100 / (1 + (_avgGain.Value / _avgLoss.Value))) : 100; + + + return rsi; + + + } +} diff --git a/lib/volatility/Rsx.cs b/lib/volatility/Rsx.cs new file mode 100644 index 00000000..6d44c26d --- /dev/null +++ b/lib/volatility/Rsx.cs @@ -0,0 +1,64 @@ +using System; + +namespace QuanTAlib; + +/// +/// Jurik's superior replacement for RSI +/// +public class Rsx : AbstractBase +{ + private readonly Rma _avgGain; + private readonly Rma _avgLoss; + private readonly Jma _rsx; + private double _prevValue, _p_prevValue; + + public Rsx(int period = 14, int phase = 0, double factor = 0.55) + { + if (period < 1) + throw new ArgumentOutOfRangeException(nameof(period)); + _avgGain = new(period); + _avgLoss = new(period); + _rsx = new(8, 100, 0.25, 3); + _index = 0; + WarmupPeriod = period + 1; + Name = $"RSX({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 == 1) + { + _prevValue = Input.Value; + } + + double change = Input.Value - _prevValue; + double gain = Math.Max(change, 0); + double loss = Math.Max(-change, 0); + _prevValue = Input.Value; + + _avgGain.Calc(gain, IsNew: Input.IsNew); + _avgLoss.Calc(loss, IsNew: Input.IsNew); + + double rsi = (_avgLoss.Value > 0) ? 100 - (100 / (1 + (_avgGain.Value / _avgLoss.Value))) : 100; + double rsx = _rsx.Calc(rsi, Input.IsNew); + + return rsx; + + + } +} diff --git a/quantower/Averages/MacdIndicator.cs b/quantower/Averages/MacdIndicator.cs index b8eb4f0b..56365f3f 100644 --- a/quantower/Averages/MacdIndicator.cs +++ b/quantower/Averages/MacdIndicator.cs @@ -62,7 +62,7 @@ public class MacdIndicator : Indicator, IWatchlistIndicator SignalSeries = new(name: $"SIGNAL", color: Color.Yellow, width: 2, style: LineStyle.Solid); HistogramSeries = new(name: $"HISTOGRAM", color: Color.White, width: 2, style: LineStyle.Solid); HistSlopeSeries = new(name: $"SLOPE", color: Color.Transparent, width: 2, style: LineStyle.Solid); - + HistSlopeSeries.Visible = false; AddLineSeries(MainSeries); AddLineSeries(SignalSeries); @@ -119,7 +119,7 @@ public class MacdIndicator : Indicator, IWatchlistIndicator for (int i = rightIndex; i < leftIndex; i++) { int barX = (int)converter.GetChartX(this.HistoricalData.Time(i)); - int barY = (int)converter.GetChartY(HistogramSeries![i]); + int barY = (int)converter.GetChartY(HistogramSeries![i]*2.0); int barY0 = (int)converter.GetChartY(0); int HistBarWidth = this.CurrentChart.BarsWidth - 2; diff --git a/quantower/Volatility/RsiIndicator.cs b/quantower/Volatility/RsiIndicator.cs new file mode 100644 index 00000000..fd0a4eec --- /dev/null +++ b/quantower/Volatility/RsiIndicator.cs @@ -0,0 +1,67 @@ +using System.Drawing; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib; + +public class RsiIndicator : Indicator, IWatchlistIndicator +{ + [InputParameter("Periods", sortIndex: 1, 1, 2000, 1, 0)] + public int Periods { get; set; } = 14; + + [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 Rsi? rsi; + protected string? SourceName; + protected LineSeries? RsiSeries; + public int MinHistoryDepths => Periods + 1; + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; + + public RsiIndicator() + { + Name = "RSI - Relative Strength Index"; + Description = "Measures the speed and magnitude of recent price changes to evaluate overbought or oversold conditions."; + SeparateWindow = true; + SourceName = Source.ToString(); + RsiSeries = new($"RSI {Periods}", Color.Blue, 2, LineStyle.Solid); + AddLineSeries(RsiSeries); + } + + protected override void OnInit() + { + rsi = new Rsi(Periods); + base.OnInit(); + } + + protected override void OnUpdate(UpdateArgs args) + { + TValue input = this.GetInputValue(args, Source); + rsi!.Calc(input); + + RsiSeries!.SetValue(rsi.Value); + RsiSeries!.SetMarker(0, Color.Transparent); + } + + public override string ShortName => $"RSI ({Periods}:{SourceName})"; + +#pragma warning disable CA1416 // Validate platform compatibility + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + this.PaintSmoothCurve(args, RsiSeries!, rsi!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); + } +} diff --git a/quantower/Volatility/RsxIndicator.cs b/quantower/Volatility/RsxIndicator.cs new file mode 100644 index 00000000..5158bc77 --- /dev/null +++ b/quantower/Volatility/RsxIndicator.cs @@ -0,0 +1,67 @@ +using System.Drawing; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib; + +public class RsxIndicator : Indicator, IWatchlistIndicator +{ + [InputParameter("Rsi Period", sortIndex: 1, 1, 2000, 1, 0)] + public int Period { get; set; } = 14; + + [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 Rsx? rsx; + protected string? SourceName; + protected LineSeries? RsxSeries; + public int MinHistoryDepths => Period + 1; + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; + + public RsxIndicator() + { + Name = "RSX - Jurik Trend Strengt Index"; + Description = "Measures the speed and magnitude of recent price changes to evaluate overbought or oversold conditions."; + SeparateWindow = true; + SourceName = Source.ToString(); + RsxSeries = new($"RSX {Period}", Color.Blue, 2, LineStyle.Solid); + AddLineSeries(RsxSeries); + } + + protected override void OnInit() + { + rsx = new(Period); + base.OnInit(); + } + + protected override void OnUpdate(UpdateArgs args) + { + TValue input = this.GetInputValue(args, Source); + rsx!.Calc(input); + + RsxSeries!.SetValue(rsx.Value); + RsxSeries!.SetMarker(0, Color.Transparent); + } + + public override string ShortName => $"RSX ({Period}:{SourceName})"; + +#pragma warning disable CA1416 // Validate platform compatibility + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + this.PaintSmoothCurve(args, RsxSeries!, rsx!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); + } +}