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

This commit is contained in:
moshferatu 2023-12-11 06:27:57 -08:00
parent b66b98dd40
commit 558321e2c5

View File

@ -33,8 +33,9 @@ namespace NinjaTrader.NinjaScript.Indicators
private static string DATE = "{date}";
private static string LEVEL = "{level}";
private Dictionary<double, string> DailyOpens = new Dictionary<double, string>(); // Open -> Date
private Dictionary<double, string> DailyCloses = new Dictionary<double, string>(); // Close -> Date
private Dictionary<double, string> DailyOpens = new Dictionary<double, string>(); // Open -> Month / Day
private Dictionary<double, string> DailyCloses = new Dictionary<double, string>(); // Close -> Month / Day
private Dictionary<string, DateTime> DailyCloseTimes = new Dictionary<string, DateTime>(); // 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<double> input, int numberOfDays, Stroke dailyOpenStroke, Stroke dailyCloseStroke, bool drawLabels, string dailyOpenLabelText, string dailyCloseLabelText, SimpleFont dailyLevelFont, Brush dailyLevelFontColor)
public DailyLevels DailyLevels(ISeries<double> 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<DailyLevels>(new DailyLevels(){ NumberOfDays = numberOfDays, DailyOpenStroke = dailyOpenStroke, DailyCloseStroke = dailyCloseStroke, DrawLabels = drawLabels, DailyOpenLabelText = dailyOpenLabelText, DailyCloseLabelText = dailyCloseLabelText, DailyLevelFont = dailyLevelFont, DailyLevelFontColor = dailyLevelFontColor }, input, ref cacheDailyLevels);
return CacheIndicator<DailyLevels>(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<double> input , int numberOfDays, Stroke dailyOpenStroke, Stroke dailyCloseStroke, bool drawLabels, string dailyOpenLabelText, string dailyCloseLabelText, SimpleFont dailyLevelFont, Brush dailyLevelFontColor)
public Indicators.DailyLevels DailyLevels(ISeries<double> 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<double> input , int numberOfDays, Stroke dailyOpenStroke, Stroke dailyCloseStroke, bool drawLabels, string dailyOpenLabelText, string dailyCloseLabelText, SimpleFont dailyLevelFont, Brush dailyLevelFontColor)
public Indicators.DailyLevels DailyLevels(ISeries<double> 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);
}
}
}