Skip to main content

Script Lifecycle and Calculation

Indicators and strategies share the ScriptBase lifecycle, but a state value and a lifecycle callback are not the same promise. Use the callbacks documented below, and do not infer a shutdown event from an enum member alone.

Reliable initialization sequence​

PhaseWhat HyperionX doesPut this work here
ConstructionCreates the script, then enters State.SetDefaults.Keep constructors empty or limited to field initialization.
SetDefaultsCalls OnStateChanged() and the legacy OnStateChange() alias.Name, version, safe parameter defaults, calculation mode, and plot definitions.
ConfiguredCalls BeforeConfigured(), the state callbacks, then AfterConfigured().Runtime series, child indicators, plot data sources, and AddDataSeries(...).
Data calculationSets Historical, Playback, or RealTime and invokes update callbacks.Read series, calculate outputs, and make strategy decisions.
TransitionCan notify OnStateChanged() as the host moves between historical and live work.Reconcile environment-specific state; do not submit an entry merely because transition occurred.
Stop or removalUses the owning host's cleanup path.Strategies: OnStrategyStopping(). Add-ons: OnUnload().
Do not wait for Finalized or Terminated

State.Finalized currently detaches the calculation context without setting the public state or notifying OnStateChanged(). The enum also contains Terminated, but the native indicator/strategy host does not establish it as a general cleanup callback. Strategy cleanup belongs in OnStrategyStopping(); add-on cleanup belongs in OnUnload().

State.DataLoaded and State.Calculated exist in the runtime model, but public scripts should not require them for essential initialization or cleanup. Calculated can be assigned by strategy internals without a state callback.

Calculation callbacks​

CallbackUse
OnBarUpdate()Main bar-series calculation. Check BarsInProgress for multi-series scripts.
OnTickUpdate(ICandle candle, ICandle tick)Tick delivery made by the calculation engine. Keep it lightweight.
OnMarketData(MarketDataEventArgs)Market-data events supplied by the host.
OnMarketDepth(MarketDepthEventArgs)Depth events supplied by the host.
OnAfterBarUpdate()Work that must follow the main bar update.
OnConnectionStatusChanged(...)Connection transition notification.

The Calculate property controls when the engine performs additional calculation updates:

  • CalculateMode.OnBarClose is the default.
  • CalculateMode.OnEachTick recalculates for each eligible tick.
  • CalculateMode.OnPriceChange recalculates when price changes.

Dedicated tick and market-data callbacks are separate delivery paths. Do not assume OnBarClose disables every tick/depth callback you explicitly subscribed to.

Series indexing​

CurrentBar is the number of processed bars. A bars-ago index of 0 means the newest processed value:

if (CurrentBar < Period || CurrentBar < 2)
return;

var newestClose = Close[0];
var previousClose = Close[1];

In the earlier documented implementation, unavailable values could return a type default instead of throwing. A zero therefore does not prove that data exists. Guard the largest bars-ago offset; the example above requires at least two processed bars as well as the chosen Period.

Unreleased strict-indexer migration

Reviewed 1.1.27 development source throws ArgumentOutOfRangeException for negative or unavailable bars-ago reads and writes. Guard with CurrentBar, the relevant CurrentBars count, or V2 IsReady(...); the largest offset n requires n + 1 processed bars. This also applies to SetValid(barsAgo). Absolute GetValueAt, GetRange, and IsValidPoint remain tolerant APIs with a different purpose. The changelog labels this migration Unreleased; see the documentation version map before assuming it applies to a public installer.

For a multi-series script:

public override void OnBarUpdate()
{
if (CurrentBars[0] < 50 ||
CurrentBars[1] < 20)
return;

if (BarsInProgress == 0)
{
// Primary-series work.
}
else if (BarsInProgress == 1)
{
// Secondary-series work.
}
}

Add secondary data in State.Configured:

AddDataSeries(DataSeriesType.Minute, 5, "ES");

Secondary updates are scheduled by their close time relative to the primary stream. Code must tolerate unequal history, missing data, and a secondary series that has not produced its first bar.

Resource ownership​

Native chart scripts do not expose one universal terminal-state callback.

  • A strategy should stop timers, cancel its own background work, and unsubscribe explicit event/depth subscriptions in OnStrategyStopping().
  • An add-on must reverse successful OnLoad() work in OnUnload().
  • An indicator should prefer script-owned series, plots, drawings, and host-managed resources. If it overrides Clear() for a documented need, it must call base.Clear().
  • Ctx.MarketData.SubscribeDepth(...) creates an explicit subscription. Pair it with UnsubscribeDepth() in the host-specific cleanup path.
  • Never keep chart, account, session, or custom-assembly objects in static fields.

Lifecycle checklist​

  • Set safe, deterministic defaults before runtime data exists.
  • Create series and child scripts once in State.Configured.
  • Guard every series and every BarsInProgress branch.
  • Keep bar, tick, depth, and rendering callbacks non-blocking.
  • Do not use a lifecycle transition as a fill or connection guarantee.
  • Give every explicit subscription, task, timer, and disposable an owner and a tested stop path.
  • Recreate the script after compiling to verify initialization and cleanup from a clean instance.

Continue with Script Context, Indicator Patterns, Strategy Patterns, and the ScriptBase reference.