If I DON'T always pass back an object in ISeries<T>, it creates phantom objects. It seems that when ISeries<T> is used with custom objects, it must ALWAYS be set to an instance of the object (or NULL). That is, you can't leave ISeries<T> empty for the current bar.
Below is sample code for the indicator and calling strategy (full code is attached). I created a simple indicator that draws a blue box at 3am each day. If a box was drawn, it sets the custom ISeries<Box> value for the current bar. If not, ISeries<Box>[0] remains null.
This will cause the phantom boxes to be drawn. If the one line is uncommented in the code below (where indicated), the phantom images disappear. See attached screenshots for examples.
Is this a bug or am I doing something wrong in my code?
INDICATOR CODE
public class BoxIndicator : Indicator { public class Box { public Box() : this(0, 0, 0, 0) { } public Box(double dblTopPrice, double dblBottomPrice, int intStartIndex, int intEndIndex) { TopPrice = dblTopPrice; BottomPrice = dblBottomPrice; StartIndex = intStartIndex; EndIndex = intEndIndex; } public double TopPrice { get; private set; } public double BottomPrice { get; private set; } public int StartIndex { get; private set; } public int EndIndex { get; private set; } } private Series<Box> _sBoxSeries; protected override void OnStateChange() { if (State == State.SetDefaults) { Description = @"Draws a box on the chart"; Name = "BoxIndicator"; IsOverlay = true; IsSuspendedWhileInactive = true; } else if (State == State.DataLoaded) { _sBoxSeries = new Series<Box>(this); } } protected override void OnBarUpdate() { if (CurrentBar < 4) return; DateTime dtNow = Time[0]; Box objBox = null; // Draw a box at 3am each day if (dtNow.Hour == 3 && dtNow.Minute == 0) objBox = new Indicators.BoxIndicator.Box(Close[0], Open[0], CurrentBar - 3, CurrentBar); // !!! UNCOMMENT THIS LINE AND IT WORKS PROPERLY !!! _sBoxSeries[0] = new Box(); // OR _sBoxSeries[0] = null; if (objBox != null) { // We have a box, so output it to series for consumption by other indicators/strategies _sBoxSeries[0] = objBox; // If we're sited on a chart, draw the actual box if (ChartControl != null) { Draw.Rectangle(this, "Box" + objBox.EndIndex.ToString(), false, objBox.EndIndex - objBox.StartIndex, objBox.TopPrice, 0, objBox.BottomPrice, Brushes.Blue, Brushes.Blue, 15); } } } #region Properties [Browsable(false), XmlIgnore()] public Series<Box> GetBox { get { return _sBoxSeries; } } #endregion }
STRATEGY CODE
public class IssueWithCustomISeriesStrat : Strategy { protected override void OnStateChange() { if (State == State.SetDefaults) { Description = @"Reproduces the issue with using custom objects as ISeries<T> value."; Name = "IssueWithCustomISeriesStrat"; Calculate = Calculate.OnBarClose; EntriesPerDirection = 1; IsInstantiatedOnEachOptimizationIteration = true; } } protected override void OnBarUpdate() { BoxIndicator.Box objBox = BoxIndicator().GetBox[0]; if (objBox != null) { Draw.Rectangle(this, "Box" + objBox.EndIndex.ToString(), false, objBox.EndIndex - objBox.StartIndex, objBox.TopPrice, 0, objBox.BottomPrice, Brushes.Blue, Brushes.Blue, 15); } } }
Comment