The OnBarUpdate() method is called for each incoming tick, or on the close of a bar (if enabled) when performing real-time calculations, and is called on each bar of a Bars object when re-calculating the indicator (For example, an indicator would be re-calculated when adding it to an existing chart that has existing price data displayed). This is the main method called for indicator calculation, and we will calculate the CCI value and set the conditions used to draw the CCI plot within this method.
The OnStateChange() method is called once before any bar data is loaded, and is used to configure the indicator (among other things).
Initializing the Indicator
The code below is automatically generated by the wizard and added to the OnStateChange() method, within State.SetDefaults. It configures the indicator for one plot and five lines, and sets the parameters entered in the wizard:
|
AddPlot(Brushes.Orange, "MyPlot");
AddLine(Brushes.DimGray, 200, "Level 2");
AddLine(Brushes.DimGray, 100, "Level 1");
AddLine(Brushes.DimGray, 0, "Zero Line");
AddLine(Brushes.DimGray, -100, "Level -1");
AddLine(Brushes.DimGray, -200, "Level -2");
|
To change the visual properties of the Zero Line, replace the fourth line in the code above with the line below. This will change the color to black and the line style to "dash:"
|
AddLine(new Stroke(Brushes.Black, DashStyleHelper.Dash, 2), 0, "Zero Line");
|
The code above uses an alternative method overload (an alternative set of arguments passed in to the AddLine() method), in order to pass in a Stroke object rather than a Brush. With a Stroke, not only can we still specify a Brush, but we have additional options to change the dash style (via DashStyleHelper) and the line width. After this change, your configured lines and plots should look like this:
|
AddPlot(Brushes.Orange, "MyCCI_Plot");
AddLine(Brushes.DimGray, 200, "Level 2");
AddLine(Brushes.DimGray, 100, "Level 1");
AddLine(new Stroke(Brushes.Black, DashStyleHelper.Dash, 2), 0, "Zero Line");
AddLine(Brushes.DimGray, -100, "Level -1");
AddLine(Brushes.DimGray, -200, "Level -2");
|
Adding Core Indicator Logic
Since this tutorial is meant to cover custom drawing and manually changing properties within an indicator, we will not go too in-depth into the core calculation logic for this custom CCI. Instead, we will copy and paste the core calculation logic from the @CCI indicator already built-in to NinjaTrader.
The @CCI indicator uses an SMA object in its calculations. To add this, copy the line below from @CCI into your custom CCI, directly below the class declaration:
|
private SMA sma;
|
Next, copy the following initialization for the SMA object into the OnStateChange() method, within State.DataLoaded:
|
sma = SMA(Typical, Period);
|
Next, copy the core calculation logic from @CCI into the OnBarUpdate() method of your custom indicator:
|
if (CurrentBar == 0)
Value[0] = 0;
else
{
double mean = 0;
double sma0 = sma[0];
for (int idx = Math.Min(CurrentBar, Period - 1); idx >= 0; idx--)
mean += Math.Abs(Typical[idx] - sma0);
Value[0] = (Typical[0] - sma0) / (mean.ApproxCompare(0) == 0 ? 1 : (0.015 * (mean / Math.Min(Period, CurrentBar + 1))));
}
|
The code for your MyCCI class should now look as follows (in addition to the using statements and class declaration) :
|
public class MyCCI : Indicator
{
private SMA sma;
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
Description = @"NinjaScript Custom Drawing Indicator Tutorial";
Name = "MyCCI";
Calculate = Calculate.OnBarClose;
IsOverlay = false;
DisplayInDataBox = true;
DrawOnPricePanel = true;
DrawHorizontalGridLines = true;
DrawVerticalGridLines = true;
PaintPriceMarkers = true;
ScaleJustification = NinjaTrader.Gui.Chart.ScaleJustification.Right;
//Disable this property if your indicator requires custom values that cumulate with each new market data event.
//See Help Guide for additional information.
IsSuspendedWhileInactive = true;
Period = 14;
AddPlot(Brushes.Orange, "MyPlot");
AddLine(Brushes.DimGray, 200, "Level 2");
AddLine(Brushes.DimGray, 100, "Level 1");
AddLine(new Stroke(Brushes.Black, DashStyleHelper.Dash,2), 0, "Zero Line");
AddLine(Brushes.DimGray, -100, "Level -1");
AddLine(Brushes.DimGray, -200, "Level -2");
}
else if (State == State.DataLoaded)
{
sma = SMA(Typical, Period);
}
}
protected override void OnBarUpdate()
{
if (CurrentBar == 0)
Value[0] = 0;
else
{
double mean = 0;
double sma0 = sma[0];
for (int idx = Math.Min(CurrentBar, Period - 1); idx >= 0; idx--)
mean += Math.Abs(Typical[idx] - sma0);
Value[0] = (Typical[0] - sma0) / (mean.ApproxCompare(0) == 0 ? 1 : (0.015 * (mean / Math.Min(Period, CurrentBar + 1))));
}
}
|
Custom Drawing
Add the following code into the OnBarUpdate() method, directly beneath the core calculation logic:
|
// if the plot value is greater than 100, paint the plot green at that bar index
if (Value[0] > 100)
PlotBrushes[0][0] = Brushes.Green;
// if the plot value is less than -100, paint the plot red at that bar index
if (Value[0] < -100)
PlotBrushes[0][0] = Brushes.Red;
// if the plot value is between 100 and -100, paint the plot orange at that bar index
if (Value[0] >= -100 && Value[0] <= 100)
PlotBrushes[0][0] = Brushes.Orange;
|
This will conditionally change the color of the CCI plot (referenced by Values[0]) based on its value. By using PlotBrushes[0][0], we are specifying that we wish to change the color of the first plot in the collection at a specific bar index (the current bar index each time the condition is triggered), and we wish for the plot the remain that color at that index, even if the plot value changes in the future. If instead we wished to change the entire plot color, we could use Plots[0].Brush.
PlotBrushes holds a collection of brushes used for the various plots in the indicator. In addition to this, there are several other collections that serve similar purposes, which can be used in the same way. Some examples of these collections are below:
BackBrushes
|
A collection of Brushes used for chart background color at specific bar indexes
|
BarBrushes
|
A collection of Brushes used to paint bars at specific indexes
|
CandleOutlineBrushes
|
A collection of Brushes used to paint candle outlines at specific indexes
|
Now that everything is in place, your class code should look as below. You are now ready to compile the indicator and configure it on a chart.
|
public class MyCCI : Indicator
{
private SMA sma;
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
Description = @"NinjaScript Custom Drawing Indicator Tutorial";
Name = "MyCCI";
Calculate = Calculate.OnBarClose;
IsOverlay = false;
DisplayInDataBox = true;
DrawOnPricePanel = true;
DrawHorizontalGridLines = true;
DrawVerticalGridLines = true;
PaintPriceMarkers = true;
ScaleJustification = NinjaTrader.Gui.Chart.ScaleJustification.Right;
//Disable this property if your indicator requires custom values that cumulate with each new market data event.
//See Help Guide for additional information.
IsSuspendedWhileInactive = true;
Period = 14;
AddPlot(Brushes.Orange, "MyPlot");
AddLine(Brushes.DimGray, 200, "Level 2");
AddLine(Brushes.DimGray, 100, "Level 1");
AddLine(new Stroke(Brushes.Black, DashStyleHelper.Dash, 2), 0, "Zero Line");
AddLine(Brushes.DimGray, -100, "Level -1");
AddLine(Brushes.DimGray, -200, "Level -2");
}
else if (State == State.DataLoaded)
{
sma = SMA(Typical, Period);
}
}
protected override void OnBarUpdate()
{
if (CurrentBar == 0)
Value[0] = 0;
else
{
double mean = 0;
double sma0 = sma[0];
for (int idx = Math.Min(CurrentBar, Period - 1); idx >= 0; idx--)
mean += Math.Abs(Typical[idx] - sma0);
Value[0] = (Typical[0] - sma0) / (mean.ApproxCompare(0) == 0 ? 1 : (0.015 * (mean / Math.Min(Period, CurrentBar + 1))));
}
if (Value[0] > 100)
PlotBrushes[0][0] = Brushes.Green;
if (Value[0] < -100)
PlotBrushes[0][0] = Brushes.Red;
if (Value[0] >= -100 && Value[0] <= 100)
PlotBrushes[0][0] = Brushes.Orange;
}
#region Properties
[NinjaScriptProperty]
[Range(1, int.MaxValue)]
[Display(Name="Period", Description="The CCI Period", Order=1, GroupName="Parameters")]
public int Period
{ get; set; }
[Browsable(false)]
[XmlIgnore]
public Series<double> MyPlot
{
get { return Values[0]; }
}
#endregion
}
|