From 558321e2c5a464fcb46a8987c9f87e51d0e8e17c Mon Sep 17 00:00:00 2001 From: moshferatu Date: Mon, 11 Dec 2023 06:27:57 -0800 Subject: [PATCH] Resolve issue where current daily close was drawn prior to actual close, disable daily closes by default in order to make the indicator more visually appealing, and begin drawing the levels from the actual open / close times --- indicators/DailyLevels.cs | 151 ++++++++++++++++++++++++-------------- 1 file changed, 97 insertions(+), 54 deletions(-) diff --git a/indicators/DailyLevels.cs b/indicators/DailyLevels.cs index 95e7ca8..d9eb27b 100644 --- a/indicators/DailyLevels.cs +++ b/indicators/DailyLevels.cs @@ -33,8 +33,9 @@ namespace NinjaTrader.NinjaScript.Indicators private static string DATE = "{date}"; private static string LEVEL = "{level}"; - private Dictionary DailyOpens = new Dictionary(); // Open -> Date - private Dictionary DailyCloses = new Dictionary(); // Close -> Date + private Dictionary DailyOpens = new Dictionary(); // Open -> Month / Day + private Dictionary DailyCloses = new Dictionary(); // Close -> Month / Day + private Dictionary DailyCloseTimes = new Dictionary(); // Month / Day -> Time protected override void OnStateChange() { @@ -53,7 +54,9 @@ namespace NinjaTrader.NinjaScript.Indicators IsSuspendedWhileInactive = true; NumberOfDays = 3; DailyOpenStroke = new Stroke(Brushes.Yellow, DashStyleHelper.Solid, 3); + ShowDailyOpens = true; DailyCloseStroke = new Stroke(Brushes.Yellow, DashStyleHelper.Solid, 3); + ShowDailyCloses = false; DrawLabels = true; DailyOpenLabelText = DATE + " Daily Open @ " + LEVEL; DailyCloseLabelText = DATE + " Daily Close @ " + LEVEL; @@ -62,9 +65,15 @@ namespace NinjaTrader.NinjaScript.Indicators } else if (State == State.Configure) { + // Using 1440 minute bars rather than daily bars. + // The desired number of days was being ignored when requesting daily bars. AddDataSeries(Instrument.FullName, new BarsPeriod { BarsPeriodType = BarsPeriodType.Minute, Value = 1440 }, NumberOfDays, Bars.TradingHours.Name, Bars.IsResetOnNewTradingDay); } + else if (State == State.Historical) + { + SetZOrder(-1); // Display behind bars on chart. + } } protected override void OnBarUpdate() @@ -72,10 +81,19 @@ namespace NinjaTrader.NinjaScript.Indicators if (BarsInProgress == DAILY_BARS && IsFirstTickOfBar) { double dailyOpen = Open[0]; - double dailyClose = Close[0]; string day = Time[0].ToString("MM/dd"); DailyOpens[dailyOpen] = day; - DailyCloses[dailyClose] = day; + + if (CurrentBar >= 1) // Make sure there is a prior day to reference. + { + double dailyClose = Close[1]; + string previousDay = Time[1].ToString("MM/dd"); + DailyCloses[dailyClose] = previousDay; + } + + // Candle times in NT are associated with the close rather than the open. + // For the daily bars, this will be the end of the market session. + DailyCloseTimes[day] = Time[0]; } } @@ -83,56 +101,71 @@ namespace NinjaTrader.NinjaScript.Indicators { base.OnRender(chartControl, chartScale); - SharpDX.Direct2D1.Brush dailyOpenBrush = DailyOpenStroke.Brush.ToDxBrush(RenderTarget); - TextFormat textFormat = DailyLevelFont.ToDirectWriteTextFormat(); SharpDX.Direct2D1.Brush textBrush = DailyLevelFontColor.ToDxBrush(RenderTarget); - foreach (double dailyOpen in DailyOpens.Keys) + if (ShowDailyOpens) { - int dailyOpenY = chartScale.GetYByValue(dailyOpen); - RenderTarget.DrawLine(new SharpDX.Vector2(ChartPanel.X, dailyOpenY), - new SharpDX.Vector2(ChartPanel.X + ChartPanel.W, dailyOpenY), - dailyOpenBrush, DailyOpenStroke.Width, DailyOpenStroke.StrokeStyle); + SharpDX.Direct2D1.Brush dailyOpenBrush = DailyOpenStroke.Brush.ToDxBrush(RenderTarget); - if (DrawLabels) + foreach (double dailyOpen in DailyOpens.Keys) { - string dailyOpenText = DailyOpenLabelText.Replace(DATE, DailyOpens[dailyOpen]).Replace(LEVEL, dailyOpen.ToString(dailyOpen % 1 == 0 ? "F0" : "F2")); - TextLayout textLayout = new TextLayout(Core.Globals.DirectWriteFactory, dailyOpenText, textFormat, 500, textFormat.FontSize); - SharpDX.Vector2 textOrigin = new SharpDX.Vector2(ChartPanel.W - textLayout.Metrics.Width - TEXT_PADDING, - ChartPanel.Y + (float)chartScale.GetYByValue(dailyOpen) - (float)textLayout.Metrics.Height - TEXT_PADDING); - RenderTarget.DrawTextLayout(textOrigin, textLayout, textBrush, SharpDX.Direct2D1.DrawTextOptions.NoSnap); + // Subtracting a day and adding a minute to the daily closing time results in the candle associated with the session open. + // Basing the open / close times on the chart bars would be better, but using the daily bars as that's where the levels come from. + int dailyOpenBarIndex = ChartBars.GetBarIdxByTime(ChartControl, DailyCloseTimes[DailyOpens[dailyOpen]].AddDays(-1).AddMinutes(1)); + int dailyOpenX = chartControl.GetXByBarIndex(ChartBars, dailyOpenBarIndex); + int dailyOpenY = chartScale.GetYByValue(dailyOpen); + RenderTarget.DrawLine(new SharpDX.Vector2(dailyOpenX, dailyOpenY), + new SharpDX.Vector2(ChartPanel.X + ChartPanel.W, dailyOpenY), + dailyOpenBrush, DailyOpenStroke.Width, DailyOpenStroke.StrokeStyle); - textLayout.Dispose(); + if (DrawLabels && (dailyOpenX < ChartPanel.X + ChartPanel.W)) + { + string dailyOpenText = DailyOpenLabelText.Replace(DATE, DailyOpens[dailyOpen]) + .Replace(LEVEL, dailyOpen.ToString(dailyOpen % 1 == 0 ? "F0" : "F2")); + TextLayout textLayout = new TextLayout(Core.Globals.DirectWriteFactory, dailyOpenText, textFormat, 500, textFormat.FontSize); + SharpDX.Vector2 textOrigin = new SharpDX.Vector2(ChartPanel.W - textLayout.Metrics.Width - TEXT_PADDING, + ChartPanel.Y + (float)chartScale.GetYByValue(dailyOpen) - (float)textLayout.Metrics.Height - TEXT_PADDING); + RenderTarget.DrawTextLayout(textOrigin, textLayout, textBrush, SharpDX.Direct2D1.DrawTextOptions.NoSnap); + + textLayout.Dispose(); + } } + + dailyOpenBrush.Dispose(); } - SharpDX.Direct2D1.Brush dailyCloseBrush = DailyCloseStroke.Brush.ToDxBrush(RenderTarget); - - foreach (double dailyClose in DailyCloses.Keys) + if (ShowDailyCloses) { - int dailyCloseY = chartScale.GetYByValue(dailyClose); - RenderTarget.DrawLine(new SharpDX.Vector2(ChartPanel.X, dailyCloseY), - new SharpDX.Vector2(ChartPanel.X + ChartPanel.W, dailyCloseY), - dailyCloseBrush, DailyCloseStroke.Width, DailyCloseStroke.StrokeStyle); + SharpDX.Direct2D1.Brush dailyCloseBrush = DailyCloseStroke.Brush.ToDxBrush(RenderTarget); - if (DrawLabels) + foreach (double dailyClose in DailyCloses.Keys) { - string dailyCloseText = DailyCloseLabelText.Replace(DATE, DailyCloses[dailyClose]).Replace(LEVEL, dailyClose.ToString(dailyClose % 1 == 0 ? "F0" : "F2")); + int dailyCloseBarIndex = ChartBars.GetBarIdxByTime(ChartControl, DailyCloseTimes[DailyCloses[dailyClose]]); + int dailyCloseX = chartControl.GetXByBarIndex(ChartBars, dailyCloseBarIndex); + int dailyCloseY = chartScale.GetYByValue(dailyClose); + RenderTarget.DrawLine(new SharpDX.Vector2(dailyCloseX, dailyCloseY), + new SharpDX.Vector2(ChartPanel.X + ChartPanel.W, dailyCloseY), + dailyCloseBrush, DailyCloseStroke.Width, DailyCloseStroke.StrokeStyle); - TextLayout textLayout = new TextLayout(Core.Globals.DirectWriteFactory, dailyCloseText, textFormat, 500, textFormat.FontSize); - SharpDX.Vector2 textOrigin = new SharpDX.Vector2(ChartPanel.W - textLayout.Metrics.Width - TEXT_PADDING, - ChartPanel.Y + (float)chartScale.GetYByValue(dailyClose) - (float)textLayout.Metrics.Height - TEXT_PADDING); - RenderTarget.DrawTextLayout(textOrigin, textLayout, textBrush, SharpDX.Direct2D1.DrawTextOptions.NoSnap); + if (DrawLabels && (dailyCloseX < ChartPanel.X + ChartPanel.W)) + { + string dailyCloseText = DailyCloseLabelText.Replace(DATE, DailyCloses[dailyClose]) + .Replace(LEVEL, dailyClose.ToString(dailyClose % 1 == 0 ? "F0" : "F2")); + TextLayout textLayout = new TextLayout(Core.Globals.DirectWriteFactory, dailyCloseText, textFormat, 500, textFormat.FontSize); + SharpDX.Vector2 textOrigin = new SharpDX.Vector2(ChartPanel.W - textLayout.Metrics.Width - TEXT_PADDING, + ChartPanel.Y + (float)chartScale.GetYByValue(dailyClose) - (float)textLayout.Metrics.Height - TEXT_PADDING); + RenderTarget.DrawTextLayout(textOrigin, textLayout, textBrush, SharpDX.Direct2D1.DrawTextOptions.NoSnap); - textLayout.Dispose(); + textLayout.Dispose(); + } } + + dailyCloseBrush.Dispose(); } - dailyOpenBrush.Dispose(); textFormat.Dispose(); textBrush.Dispose(); - dailyCloseBrush.Dispose(); } public override string DisplayName @@ -147,38 +180,48 @@ namespace NinjaTrader.NinjaScript.Indicators { get; set; } [NinjaScriptProperty] - [Display(Name = "Daily Open", Description = "Daily open level drawn on the chart", Order = 2, GroupName = "Daily Levels")] + [Display(Name = "Show Daily Opens", Description = "Show daily opens on the chart", Order = 2, GroupName = "Daily Levels")] + public bool ShowDailyOpens + { get; set; } + + [NinjaScriptProperty] + [Display(Name = "Daily Open", Description = "Daily open level drawn on the chart", Order = 3, GroupName = "Daily Levels")] public Stroke DailyOpenStroke { get; set; } [NinjaScriptProperty] - [Display(Name = "Daily Close", Description = "Daily close level drawn on the chart", Order = 3, GroupName = "Daily Levels")] + [Display(Name = "Show Daily Closes", Description = "Show daily closes on the chart", Order = 4, GroupName = "Daily Levels")] + public bool ShowDailyCloses + { get; set; } + + [NinjaScriptProperty] + [Display(Name = "Daily Close", Description = "Daily close level drawn on the chart", Order = 5, GroupName = "Daily Levels")] public Stroke DailyCloseStroke { get; set; } [NinjaScriptProperty] - [Display(Name = "Draw Labels", Order = 1, GroupName = "Labels")] + [Display(Name = "Draw Labels", Description = "Show labels on the chart", Order = 1, GroupName = "Labels")] public bool DrawLabels { get; set; } [NinjaScriptProperty] - [Display(Name = "Daily Open Label Text", Order = 2, GroupName = "Labels")] + [Display(Name = "Daily Open Label Text", Description = "Text displayed for daily opens", Order = 2, GroupName = "Labels")] public string DailyOpenLabelText { get; set; } [NinjaScriptProperty] - [Display(Name = "DailyClose Label Text", Order = 3, GroupName = "Labels")] + [Display(Name = "Daily Close Label Text", Description = "Text displayed for daily closes", Order = 3, GroupName = "Labels")] public string DailyCloseLabelText { get; set; } [NinjaScriptProperty] - [Display(Name = "Font", Description = "Font used to display daily levels", Order = 3, GroupName = "Labels")] + [Display(Name = "Font", Description = "Font used to display daily levels", Order = 4, GroupName = "Labels")] public SimpleFont DailyLevelFont { get; set; } [XmlIgnore] [NinjaScriptProperty] - [Display(Name = "Font Color", Description = "Color of the text used to label the daily levels", Order = 4, GroupName = "Labels")] + [Display(Name = "Font Color", Description = "Color of the text used to label the daily levels", Order = 5, GroupName = "Labels")] public Brush DailyLevelFontColor { get; set; } @@ -198,18 +241,18 @@ namespace NinjaTrader.NinjaScript.Indicators public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase { private DailyLevels[] cacheDailyLevels; - public DailyLevels DailyLevels(int numberOfDays, Stroke dailyOpenStroke, Stroke dailyCloseStroke, bool drawLabels, string dailyOpenLabelText, string dailyCloseLabelText, SimpleFont dailyLevelFont, Brush dailyLevelFontColor) + public DailyLevels DailyLevels(int numberOfDays, bool showDailyOpens, Stroke dailyOpenStroke, bool showDailyCloses, Stroke dailyCloseStroke, bool drawLabels, string dailyOpenLabelText, string dailyCloseLabelText, SimpleFont dailyLevelFont, Brush dailyLevelFontColor) { - return DailyLevels(Input, numberOfDays, dailyOpenStroke, dailyCloseStroke, drawLabels, dailyOpenLabelText, dailyCloseLabelText, dailyLevelFont, dailyLevelFontColor); + return DailyLevels(Input, numberOfDays, showDailyOpens, dailyOpenStroke, showDailyCloses, dailyCloseStroke, drawLabels, dailyOpenLabelText, dailyCloseLabelText, dailyLevelFont, dailyLevelFontColor); } - public DailyLevels DailyLevels(ISeries input, int numberOfDays, Stroke dailyOpenStroke, Stroke dailyCloseStroke, bool drawLabels, string dailyOpenLabelText, string dailyCloseLabelText, SimpleFont dailyLevelFont, Brush dailyLevelFontColor) + public DailyLevels DailyLevels(ISeries input, int numberOfDays, bool showDailyOpens, Stroke dailyOpenStroke, bool showDailyCloses, Stroke dailyCloseStroke, bool drawLabels, string dailyOpenLabelText, string dailyCloseLabelText, SimpleFont dailyLevelFont, Brush dailyLevelFontColor) { if (cacheDailyLevels != null) for (int idx = 0; idx < cacheDailyLevels.Length; idx++) - if (cacheDailyLevels[idx] != null && cacheDailyLevels[idx].NumberOfDays == numberOfDays && cacheDailyLevels[idx].DailyOpenStroke == dailyOpenStroke && cacheDailyLevels[idx].DailyCloseStroke == dailyCloseStroke && cacheDailyLevels[idx].DrawLabels == drawLabels && cacheDailyLevels[idx].DailyOpenLabelText == dailyOpenLabelText && cacheDailyLevels[idx].DailyCloseLabelText == dailyCloseLabelText && cacheDailyLevels[idx].DailyLevelFont == dailyLevelFont && cacheDailyLevels[idx].DailyLevelFontColor == dailyLevelFontColor && cacheDailyLevels[idx].EqualsInput(input)) + if (cacheDailyLevels[idx] != null && cacheDailyLevels[idx].NumberOfDays == numberOfDays && cacheDailyLevels[idx].ShowDailyOpens == showDailyOpens && cacheDailyLevels[idx].DailyOpenStroke == dailyOpenStroke && cacheDailyLevels[idx].ShowDailyCloses == showDailyCloses && cacheDailyLevels[idx].DailyCloseStroke == dailyCloseStroke && cacheDailyLevels[idx].DrawLabels == drawLabels && cacheDailyLevels[idx].DailyOpenLabelText == dailyOpenLabelText && cacheDailyLevels[idx].DailyCloseLabelText == dailyCloseLabelText && cacheDailyLevels[idx].DailyLevelFont == dailyLevelFont && cacheDailyLevels[idx].DailyLevelFontColor == dailyLevelFontColor && cacheDailyLevels[idx].EqualsInput(input)) return cacheDailyLevels[idx]; - return CacheIndicator(new DailyLevels(){ NumberOfDays = numberOfDays, DailyOpenStroke = dailyOpenStroke, DailyCloseStroke = dailyCloseStroke, DrawLabels = drawLabels, DailyOpenLabelText = dailyOpenLabelText, DailyCloseLabelText = dailyCloseLabelText, DailyLevelFont = dailyLevelFont, DailyLevelFontColor = dailyLevelFontColor }, input, ref cacheDailyLevels); + return CacheIndicator(new DailyLevels(){ NumberOfDays = numberOfDays, ShowDailyOpens = showDailyOpens, DailyOpenStroke = dailyOpenStroke, ShowDailyCloses = showDailyCloses, DailyCloseStroke = dailyCloseStroke, DrawLabels = drawLabels, DailyOpenLabelText = dailyOpenLabelText, DailyCloseLabelText = dailyCloseLabelText, DailyLevelFont = dailyLevelFont, DailyLevelFontColor = dailyLevelFontColor }, input, ref cacheDailyLevels); } } } @@ -218,14 +261,14 @@ namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns { public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase { - public Indicators.DailyLevels DailyLevels(int numberOfDays, Stroke dailyOpenStroke, Stroke dailyCloseStroke, bool drawLabels, string dailyOpenLabelText, string dailyCloseLabelText, SimpleFont dailyLevelFont, Brush dailyLevelFontColor) + public Indicators.DailyLevels DailyLevels(int numberOfDays, bool showDailyOpens, Stroke dailyOpenStroke, bool showDailyCloses, Stroke dailyCloseStroke, bool drawLabels, string dailyOpenLabelText, string dailyCloseLabelText, SimpleFont dailyLevelFont, Brush dailyLevelFontColor) { - return indicator.DailyLevels(Input, numberOfDays, dailyOpenStroke, dailyCloseStroke, drawLabels, dailyOpenLabelText, dailyCloseLabelText, dailyLevelFont, dailyLevelFontColor); + return indicator.DailyLevels(Input, numberOfDays, showDailyOpens, dailyOpenStroke, showDailyCloses, dailyCloseStroke, drawLabels, dailyOpenLabelText, dailyCloseLabelText, dailyLevelFont, dailyLevelFontColor); } - public Indicators.DailyLevels DailyLevels(ISeries input , int numberOfDays, Stroke dailyOpenStroke, Stroke dailyCloseStroke, bool drawLabels, string dailyOpenLabelText, string dailyCloseLabelText, SimpleFont dailyLevelFont, Brush dailyLevelFontColor) + public Indicators.DailyLevels DailyLevels(ISeries input , int numberOfDays, bool showDailyOpens, Stroke dailyOpenStroke, bool showDailyCloses, Stroke dailyCloseStroke, bool drawLabels, string dailyOpenLabelText, string dailyCloseLabelText, SimpleFont dailyLevelFont, Brush dailyLevelFontColor) { - return indicator.DailyLevels(input, numberOfDays, dailyOpenStroke, dailyCloseStroke, drawLabels, dailyOpenLabelText, dailyCloseLabelText, dailyLevelFont, dailyLevelFontColor); + return indicator.DailyLevels(input, numberOfDays, showDailyOpens, dailyOpenStroke, showDailyCloses, dailyCloseStroke, drawLabels, dailyOpenLabelText, dailyCloseLabelText, dailyLevelFont, dailyLevelFontColor); } } } @@ -234,14 +277,14 @@ namespace NinjaTrader.NinjaScript.Strategies { public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase { - public Indicators.DailyLevels DailyLevels(int numberOfDays, Stroke dailyOpenStroke, Stroke dailyCloseStroke, bool drawLabels, string dailyOpenLabelText, string dailyCloseLabelText, SimpleFont dailyLevelFont, Brush dailyLevelFontColor) + public Indicators.DailyLevels DailyLevels(int numberOfDays, bool showDailyOpens, Stroke dailyOpenStroke, bool showDailyCloses, Stroke dailyCloseStroke, bool drawLabels, string dailyOpenLabelText, string dailyCloseLabelText, SimpleFont dailyLevelFont, Brush dailyLevelFontColor) { - return indicator.DailyLevels(Input, numberOfDays, dailyOpenStroke, dailyCloseStroke, drawLabels, dailyOpenLabelText, dailyCloseLabelText, dailyLevelFont, dailyLevelFontColor); + return indicator.DailyLevels(Input, numberOfDays, showDailyOpens, dailyOpenStroke, showDailyCloses, dailyCloseStroke, drawLabels, dailyOpenLabelText, dailyCloseLabelText, dailyLevelFont, dailyLevelFontColor); } - public Indicators.DailyLevels DailyLevels(ISeries input , int numberOfDays, Stroke dailyOpenStroke, Stroke dailyCloseStroke, bool drawLabels, string dailyOpenLabelText, string dailyCloseLabelText, SimpleFont dailyLevelFont, Brush dailyLevelFontColor) + public Indicators.DailyLevels DailyLevels(ISeries input , int numberOfDays, bool showDailyOpens, Stroke dailyOpenStroke, bool showDailyCloses, Stroke dailyCloseStroke, bool drawLabels, string dailyOpenLabelText, string dailyCloseLabelText, SimpleFont dailyLevelFont, Brush dailyLevelFontColor) { - return indicator.DailyLevels(input, numberOfDays, dailyOpenStroke, dailyCloseStroke, drawLabels, dailyOpenLabelText, dailyCloseLabelText, dailyLevelFont, dailyLevelFontColor); + return indicator.DailyLevels(input, numberOfDays, showDailyOpens, dailyOpenStroke, showDailyCloses, dailyCloseStroke, drawLabels, dailyOpenLabelText, dailyCloseLabelText, dailyLevelFont, dailyLevelFontColor); } } }