Skip to main content

Indicator Development Patterns

A production indicator has three separate responsibilities: calculate deterministic values, expose readable output, and render optional visuals. Keeping those layers separate makes the indicator reusable by charts, strategies, and research.

  1. Define identity, defaults, and plots in State.SetDefaults.
  2. Create output series and child indicators in State.Configured.
  3. Guard the required history in OnBarUpdate().
  4. Write output series even when no chart is attached.
  5. Render from those outputs instead of recalculating the model in the renderer.
private Plot _plot = null!;

[Browsable(false)]
[XmlIgnore]
public Series<double> Value { get; private set; } = null!;

public override void OnStateChanged()
{
if (State == State.SetDefaults)
{
Name = "Range Percent";
Period = 14;
_plot = new Plot { Name = "Range Percent" };
AddPanePlot(_plot);
}
else if (State == State.Configured)
{
Value = new Series<double>();
AddSeries(Value);
_plot.DataSource = Value;
}
}

public override void OnBarUpdate()
{
if (CurrentBar < Period)
return;

var denominator = System.Math.Max(System.Math.Abs(Close[0]), TickSize);
Value[0] = 100.0 * (High[0] - Low[0]) / denominator;
}

The example guards both history and a zero divisor. In real code, decide explicitly whether an unavailable output should remain unset, repeat the last value, or receive a neutral value.

Reuse another indicator

Use a generated helper in State.Configured:

private MyAverage _fast = null!;
private MyAverage _slow = null!;

public override void OnStateChanged()
{
if (State == State.Configured)
{
_fast = MyAverage(Close, 10);
_slow = MyAverage(Close, 30);
}
}

Do not create child indicators inside OnBarUpdate(). Generated helpers cache by input, options, and parameter values; see Parameters and Generated Helpers.

Plots, bar styling, drawings, and overlays

Choose the narrowest output:

NeedSurface
Numeric output usable by another scriptRegistered Series<double>.
Line or histogram driven by a seriesPlot connected through DataSource.
Candle or background emphasisBarBrush, CandleOutlineBrush, or BackBrushAll.
Tagged annotationDocumented Draw or Ctx.Drawing method.
Custom visible-range geometryIChartOverlayRenderer.OnRender(...).

Rendering can occur often and only has chart context. Keep it allocation-conscious, restrict loops to the visible range, and never make it the only place a signal or research value exists.

Structured analysis events

An indicator can implement IIndicatorAnalysisProvider when local analysis tools need recent human-readable events:

public IReadOnlyList<IndicatorAnalysisEvent> GetRecentAnalysisEvents(int maxCount)
{
return _events
.TakeLast(System.Math.Max(0, maxCount))
.ToArray();
}

Each IndicatorAnalysisEvent can carry BarIndex, Time, Kind, Direction, Label, Price, EndPrice, Value, and Details. Return bounded, read-only data. Do not expose account secrets or retain unbounded event history.

This optional interface augments an indicator; it is not a separate Code Lab extension type.

Research features

Implement IResearchFeatureProvider to add named numeric features for an absolute bar index:

public IEnumerable<ResearchIndicatorFeature> GetResearchFeatures(int barIndex)
{
yield return new ResearchIndicatorFeature
{
Name = "slope",
Value = CalculateSlopeAt(barIndex)
};
}

Dataset construction also reads registered output series and eligible public series properties. Provider feature names are combined with the indicator identity by the research pipeline. Keep names stable, values finite, and calculations free of future data. Invalid/non-finite values and provider exceptions can be omitted from a dataset, so validate the exported row count and schema.

Tick and depth work

Use CalculateMode.OnEachTick or OnPriceChange only when the model requires intrabar recalculation. Dedicated OnTickUpdate, OnMarketData, and OnMarketDepth callbacks should update small pieces of state and let OnBarUpdate or rendering consume them.

If you call Ctx.MarketData.SubscribeDepth(...), define and test the corresponding unsubscribe path. An indicator has no guaranteed native terminal-state callback; prefer host-managed data hooks and avoid long-lived background resources.

Failure and validation

The live host isolates repeated callback failures. A live indicator is disabled after ten consecutive failures. That is a final containment mechanism, not normal control flow.

Validate:

  • Insufficient and exactly sufficient history.
  • Zero, negative, missing, and non-finite source values.
  • Parameter changes and two instances with different inputs.
  • Historical load, incremental updates, and reload after compilation.
  • Chartless calculation when the output is intended for research.
  • Scrolling, zooming, resizing, and interval changes for rendered indicators.
  • No look-ahead use of later bars or future labels in a tradable output.

Continue with Build Your First Indicator, Indicator reference, and Chart Rendering.