From 295fc85a2acfae39acf7820ee887bdba16433bfd Mon Sep 17 00:00:00 2001 From: moshferatu Date: Fri, 10 Nov 2023 09:01:30 -0800 Subject: [PATCH] Add WIP RSI Dashboard indicator, credit to @BestForexMethod --- indicators/RSIDashboard.cs | 286 +++++++++++++++++++++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 indicators/RSIDashboard.cs diff --git a/indicators/RSIDashboard.cs b/indicators/RSIDashboard.cs new file mode 100644 index 0000000..d494438 --- /dev/null +++ b/indicators/RSIDashboard.cs @@ -0,0 +1,286 @@ +#region Using declarations +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Windows.Media; +using NinjaTrader.Cbi; +using NinjaTrader.Gui; +using NinjaTrader.Gui.Chart; +using NinjaTrader.NinjaScript; +using NinjaTrader.Data; +using NinjaTrader.Gui.Tools; +using SharpDX; +using SharpDX.DirectWrite; +using System.Xml.Serialization; +#endregion + +namespace NinjaTrader.NinjaScript.Indicators +{ + public class RSIDashboard : Indicator + { + private const int MTFBarsStartIndex = 1; + + private Dictionary RSIIndicators = new Dictionary(); + + private string[] TimeFrames = new[] { "1 Minute", "5 Minutes", "30 Minutes", "1 Hour", "4 Hours", "Daily", "Weekly" }; + private double[] RSIValues = new double[7]; + + protected override void OnStateChange() + { + if (State == State.SetDefaults) + { + Description = @"Displays the RSI values of multiple time frames."; + Name = "RSI Dashboard"; + Calculate = Calculate.OnBarClose; + IsOverlay = true; + RSIPeriod = 14; + RSISmoothing = 3; + UpperThreshold = 65; + LowerThreshold = 35; + AboveThresholdColor = Brushes.LimeGreen; + BelowThresholdColor = Brushes.Red; + BetweenThresholdsColor = Brushes.Transparent; + HeaderColor = Brushes.Yellow; + } + else if (State == State.Configure) + { + for (int i = 0; i < TimeFrames.Length; i++) + { + if (TimeFrames[i].Contains("Daily")) + AddDataSeries(Instrument.FullName, new BarsPeriod { BarsPeriodType = BarsPeriodType.Day, Value = 1 }, + 365, Instrument.MasterInstrument.TradingHours.Name, Bars.IsResetOnNewTradingDay); + else if (TimeFrames[i].Contains("Weekly")) + AddDataSeries(Instrument.FullName, new BarsPeriod { BarsPeriodType = BarsPeriodType.Week, Value = 1 }, + 52, Instrument.MasterInstrument.TradingHours.Name, Bars.IsResetOnNewTradingDay); + else + AddDataSeries(Instrument.FullName, BarsPeriodType.Minute, ConvertTimeFrameToMinutes(TimeFrames[i])); + } + } + else if (State == State.DataLoaded) + { + int i = MTFBarsStartIndex; + foreach (string timeFrame in TimeFrames) + { + // BarsArray[0] is the chart bar series, BarsArray[1] is the 1 minute bar series, etc. + RSI rsi = RSI(BarsArray[i], RSIPeriod, RSISmoothing); + RSIIndicators[timeFrame] = rsi; + i++; + } + } + else if (State == State.Historical) + { + SetZOrder(-1); // Display behind bars on chart. + } + } + + private int ConvertTimeFrameToMinutes(string timeFrame) + { + var numberPart = timeFrame.Split(' ')[0]; + int timeFrameValue; + if (int.TryParse(numberPart, out timeFrameValue)) + { + if (timeFrame.Contains("Minute")) + return timeFrameValue; + else if (timeFrame.Contains("Hour")) + return timeFrameValue * 60; + } + + throw new ArgumentException("Invalid time frame format: " + timeFrame); + } + + protected override void OnBarUpdate() + { + RSI rsiValue; + for (int i = MTFBarsStartIndex; i < TimeFrames.Length + 1; i++) + { + if (CurrentBars[i] < 1) + return; + + if (RSIIndicators.TryGetValue(TimeFrames[i - 1], out rsiValue)) + RSIValues[i - 1] = rsiValue.Value[0]; + else + RSIValues[i - 1] = 0; // Default value. + } + } + + protected override void OnRender(ChartControl chartControl, ChartScale chartScale) + { + base.OnRender(chartControl, chartScale); + + // Calculate maximum column width based on time frame text. + int maxColWidth = 0; + foreach (var timeFrame in TimeFrames) + { + var size = MeasureString(timeFrame, new TextFormat(Core.Globals.DirectWriteFactory, "Arial", 12)); + maxColWidth = Math.Max(maxColWidth, size.Width); + } + + maxColWidth += 40; // Adding padding of 40 pixels (20 on each side). + + int x = ChartPanel.W - (maxColWidth * TimeFrames.Length) - 20; // Adjust starting position due to padding. + int y = 10; + int rowHeight = 20; + int headerHeight = 20; + + // Draw header row. + foreach (var timeFrame in TimeFrames) + { + RectangleF headerRect = new RectangleF(x, y, maxColWidth, headerHeight); + RenderTarget.FillRectangle(headerRect, HeaderColor.ToDxBrush(RenderTarget)); + + var textFormat = new TextFormat(Core.Globals.DirectWriteFactory, "Arial", FontWeight.Normal, FontStyle.Normal, 12); + + var size = MeasureString(timeFrame, textFormat); + int textX = x + (maxColWidth - size.Width) / 2; // Center the text horizontally. + int textY = y + (headerHeight - size.Height) / 2; // Center the text vertically. + + DrawText(timeFrame, textX, textY, Brushes.Black, textFormat, headerHeight); + x += maxColWidth; + } + + // Reset x coordinate for RSI values. + x = ChartPanel.W - (maxColWidth * TimeFrames.Length) - 20; + y += headerHeight; + + // Draw RSI value row. + for (int i = 0; i < RSIValues.Length; i++) + { + var rsiText = RSIValues[i].ToString("F2"); // Format RSI value to two decimal places. + var size = MeasureString(rsiText, new TextFormat(Core.Globals.DirectWriteFactory, "Arial", 12)); + int textX = x + (maxColWidth - size.Width) / 2; // Center the text horizontally. + int textY = y + (rowHeight - size.Height) / 2; // Center the text vertically. + + Brush bgColor = BetweenThresholdsColor; + if (RSIValues[i] > UpperThreshold) bgColor = AboveThresholdColor; + else if (RSIValues[i] < LowerThreshold) bgColor = BelowThresholdColor; + + RectangleF rect = new RectangleF(x, y, maxColWidth, rowHeight); + RenderTarget.FillRectangle(rect, bgColor.ToDxBrush(RenderTarget)); + DrawText(rsiText, textX, textY, Brushes.White, new TextFormat(Core.Globals.DirectWriteFactory, "Arial", 12), rowHeight); + x += maxColWidth; + } + } + + private Size2 MeasureString(string text, TextFormat textFormat) + { + using (var textLayout = new TextLayout(Core.Globals.DirectWriteFactory, text, textFormat, float.PositiveInfinity, float.PositiveInfinity)) + { + return new Size2((int)Math.Ceiling(textLayout.Metrics.Width), (int)Math.Ceiling(textLayout.Metrics.Height)); + } + } + + private void DrawText(string text, int x, int y, Brush brush, TextFormat textFormat, int height) + { + TextLayout textLayout = new TextLayout(Core.Globals.DirectWriteFactory, text, textFormat, 500, height); + Vector2 textOrigin = new Vector2(x, y); + RenderTarget.DrawTextLayout(textOrigin, textLayout, brush.ToDxBrush(RenderTarget)); + textLayout.Dispose(); + } + + public override string DisplayName + { + get { return Name; } + } + + #region Properties + [NinjaScriptProperty] + [Range(1, int.MaxValue)] + [Display(Name = "RSI Period", GroupName = "RSI Dashboard", Order = 1)] + public int RSIPeriod { get; set; } + + [NinjaScriptProperty] + [Range(1, int.MaxValue)] + [Display(Name = "RSI Smoothing", GroupName = "RSI Dashboard", Order = 2)] + public int RSISmoothing { get; set; } + + [NinjaScriptProperty] + [Range(0, 100)] + [Display(Name = "Upper Threshold", GroupName = "RSI Dashboard", Order = 3)] + public double UpperThreshold { get; set; } + + [NinjaScriptProperty] + [Range(0, 100)] + [Display(Name = "Lower Threshold", GroupName = "RSI Dashboard", Order = 4)] + public double LowerThreshold { get; set; } + + [NinjaScriptProperty] + [XmlIgnore] + [Display(Name = "Above Threshold Color", Order = 5, GroupName = "RSI Dashboard")] + public Brush AboveThresholdColor { get; set; } + + [NinjaScriptProperty] + [XmlIgnore] + [Display(Name = "Below Threshold Color", Order = 6, GroupName = "RSI Dashboard")] + public Brush BelowThresholdColor { get; set; } + + [NinjaScriptProperty] + [XmlIgnore] + [Display(Name = "Between Thresholds Color", Order = 7, GroupName = "RSI Dashboard")] + public Brush BetweenThresholdsColor { get; set; } + + [NinjaScriptProperty] + [XmlIgnore] + [Display(Name = "Header Color", Order = 8, GroupName = "RSI Dashboard")] + public Brush HeaderColor { get; set; } + #endregion + } +} + +#region NinjaScript generated code. Neither change nor remove. + +namespace NinjaTrader.NinjaScript.Indicators +{ + public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase + { + private RSIDashboard[] cacheRSIDashboard; + public RSIDashboard RSIDashboard(int rSIPeriod, int rSISmoothing, double upperThreshold, double lowerThreshold, Brush aboveThresholdColor, Brush belowThresholdColor, Brush betweenThresholdsColor, Brush headerColor) + { + return RSIDashboard(Input, rSIPeriod, rSISmoothing, upperThreshold, lowerThreshold, aboveThresholdColor, belowThresholdColor, betweenThresholdsColor, headerColor); + } + + public RSIDashboard RSIDashboard(ISeries input, int rSIPeriod, int rSISmoothing, double upperThreshold, double lowerThreshold, Brush aboveThresholdColor, Brush belowThresholdColor, Brush betweenThresholdsColor, Brush headerColor) + { + if (cacheRSIDashboard != null) + for (int idx = 0; idx < cacheRSIDashboard.Length; idx++) + if (cacheRSIDashboard[idx] != null && cacheRSIDashboard[idx].RSIPeriod == rSIPeriod && cacheRSIDashboard[idx].RSISmoothing == rSISmoothing && cacheRSIDashboard[idx].UpperThreshold == upperThreshold && cacheRSIDashboard[idx].LowerThreshold == lowerThreshold && cacheRSIDashboard[idx].AboveThresholdColor == aboveThresholdColor && cacheRSIDashboard[idx].BelowThresholdColor == belowThresholdColor && cacheRSIDashboard[idx].BetweenThresholdsColor == betweenThresholdsColor && cacheRSIDashboard[idx].HeaderColor == headerColor && cacheRSIDashboard[idx].EqualsInput(input)) + return cacheRSIDashboard[idx]; + return CacheIndicator(new RSIDashboard(){ RSIPeriod = rSIPeriod, RSISmoothing = rSISmoothing, UpperThreshold = upperThreshold, LowerThreshold = lowerThreshold, AboveThresholdColor = aboveThresholdColor, BelowThresholdColor = belowThresholdColor, BetweenThresholdsColor = betweenThresholdsColor, HeaderColor = headerColor }, input, ref cacheRSIDashboard); + } + } +} + +namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns +{ + public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase + { + public Indicators.RSIDashboard RSIDashboard(int rSIPeriod, int rSISmoothing, double upperThreshold, double lowerThreshold, Brush aboveThresholdColor, Brush belowThresholdColor, Brush betweenThresholdsColor, Brush headerColor) + { + return indicator.RSIDashboard(Input, rSIPeriod, rSISmoothing, upperThreshold, lowerThreshold, aboveThresholdColor, belowThresholdColor, betweenThresholdsColor, headerColor); + } + + public Indicators.RSIDashboard RSIDashboard(ISeries input , int rSIPeriod, int rSISmoothing, double upperThreshold, double lowerThreshold, Brush aboveThresholdColor, Brush belowThresholdColor, Brush betweenThresholdsColor, Brush headerColor) + { + return indicator.RSIDashboard(input, rSIPeriod, rSISmoothing, upperThreshold, lowerThreshold, aboveThresholdColor, belowThresholdColor, betweenThresholdsColor, headerColor); + } + } +} + +namespace NinjaTrader.NinjaScript.Strategies +{ + public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase + { + public Indicators.RSIDashboard RSIDashboard(int rSIPeriod, int rSISmoothing, double upperThreshold, double lowerThreshold, Brush aboveThresholdColor, Brush belowThresholdColor, Brush betweenThresholdsColor, Brush headerColor) + { + return indicator.RSIDashboard(Input, rSIPeriod, rSISmoothing, upperThreshold, lowerThreshold, aboveThresholdColor, belowThresholdColor, betweenThresholdsColor, headerColor); + } + + public Indicators.RSIDashboard RSIDashboard(ISeries input , int rSIPeriod, int rSISmoothing, double upperThreshold, double lowerThreshold, Brush aboveThresholdColor, Brush belowThresholdColor, Brush betweenThresholdsColor, Brush headerColor) + { + return indicator.RSIDashboard(input, rSIPeriod, rSISmoothing, upperThreshold, lowerThreshold, aboveThresholdColor, belowThresholdColor, betweenThresholdsColor, headerColor); + } + } +} + +#endregion