HyperionXScript Best Practices
Use these rules for every Code Lab review. They apply to code written by people and by AI coding agents.
Use the right lifecycle stage
| Work | Correct place |
|---|---|
| Name, version, parameter defaults, plot definitions | State.SetDefaults |
| Runtime series, child indicators, plot data sources, extra data series | State.Configured |
| Current-bar calculations | OnBarUpdate() |
| Tick-specific lightweight logic | OnTickUpdate(...) or documented market-data hooks |
| Custom chart geometry | IChartOverlayRenderer.OnRender(...) |
| Strategy-owned resource cleanup | OnStrategyStopping() |
| Add-on resource acquisition | AddonBase.OnLoad() |
| Add-on unsubscribe, cancellation, disposal | AddonBase.OnUnload() |
Do not create a new series, plot, child indicator, timer, control, or service on every bar.
Do not use State.Finalized or State.Terminated as a general native-script cleanup promise. See Script Lifecycle and Calculation.
Guard every indexed read
var requiredBars = System.Math.Max(FastPeriod, SlowPeriod) + 1;
if (CurrentBar < requiredBars)
return;
For multiple series, guard each index:
if (CurrentBars[0] < 50 || CurrentBars[1] < 20)
return;
Then route the callback with BarsInProgress. A primary-series guard does not prove the secondary series is ready.
Keep hot paths small
OnBarUpdate(), tick callbacks, market-depth callbacks, and rendering can run frequently.
Avoid:
- File or network I/O.
Sleep, synchronous waits, or blocking locks.- Recalculating all historical candles on every update.
- Heavy LINQ or repeated allocations in tick/render paths.
- Creating WPF controls or brushes per update.
- Logging every tick in normal operation.
Allocate once, retain only the state you need, update incrementally, and constrain rendering loops to visible bars.
Treat time explicitly
Inside an indicator or strategy, DateTime is a bar timestamp series.
System.DateTime barTime = DateTime[0];
System.DateTime localNow = System.DateTime.Now;
System.DateTime utcNow = System.DateTime.UtcNow;
Do not write DateTime.Now when you intend the .NET type; the inherited series name can win name resolution.
Prefer the documented facade
For new indicator and strategy code, use Ctx where it provides the capability:
Ctx.Chartfor visible range, coordinate conversion, and invalidation.Ctx.MarketDatafor candles, bid/ask, depth, and bar timing.Ctx.Drawingfor script-owned HUD drawings and cleanup.Ctx.Ordersfor strategy-only managed order actions.Ctx.AccountandCtx.Positionfor read-only state.Ctx.Logfor structured diagnostics.
The facade is narrower than internal platform objects and makes intent easier to review. Use lower-level APIs when the reference explicitly requires them.
Separate signal, execution, and research logic
A maintainable trading system has clear boundaries:
Indicator or signal calculation
↓
Strategy entry/exit decision
↓
Order and execution lifecycle
↓
Money management + commission model
↓
Backtest / validation / optimization evidence
Do not make an indicator place orders. Do not hide broker calls inside a fitness function. Do not select parameters using a cost-free backtest and add fees only afterward.
Track orders as asynchronous state
An order-submission call is not a fill confirmation.
- Use meaningful and stable signal names.
- Set managed stops and targets before or with their entry flow.
- Use OCO groups for paired exits where appropriate.
- Track accepted, working, filled, canceled, and rejected states in order callbacks.
- Handle partial fills and quantity changes.
- Clear stale order references after terminal states.
- Prevent repeated entry or exit submissions from the same bar condition.
Always validate on playback or simulation with an explicitly selected LocalPaper account before enabling a live account. Playback does not change the selected account, but the current release-candidate guard rejects Broker and Server Paper submissions, changes, and cancellations while Playback is active. Treat that guard as a backstop, not as account selection.
Own and release resources
Any code that subscribes, starts, opens, or allocates must define who stops, closes, unsubscribes, or disposes it.
For add-ons, make OnLoad() transactional: if acquisition fails halfway, roll back partial resources inside OnLoad() before rethrowing. A failed load is not retained and must not rely on a later OnUnload() call.
For chart scripts, remove script-owned drawings and release renderer resources when the documented lifecycle requires it. Do not retain chart or session objects in static fields.
Pair Ctx.MarketData.SubscribeDepth(...) with UnsubscribeDepth() in the owning host's cleanup path.
Keep UI work on the UI thread
Add-ons can use Context.Dispatcher for documented UI integration. Dispatch only the UI mutation; keep I/O and long computation off the dispatcher.
Indicators and strategies should use plot, draw, render-context, and invalidation APIs rather than mutating chart WPF internals.
Use HyperionX APIs, not look-alikes
Do not assume these external-platform concepts exist:
Draw.TextFixedTextPositionIsOverlay- External namespaces, attributes, lifecycle states, or generated cache regions
- Order helpers that are absent from the current HyperionX reference
Use NinjaScript Migration when porting an idea, and compile after each converted layer.
Protect secrets and user accounts
- Never put credentials, tokens, private keys, or seed phrases in source or script parameters.
- Do not print secrets or full authenticated request payloads.
- Keep external integrations behind documented permission and API boundaries.
- Default strategy examples to simulation.
- Validate quantity, price, account, connection, and provider capabilities before order actions.
- Degrade safely when market data, depth, or account state is unavailable.
Review checklist
- Correct category, namespace, and base type.
- Unique public class and matching file name.
- Defaults and runtime setup are in the correct states.
- All indexed reads have sufficient guards.
- Hot callbacks are non-blocking and allocation-conscious.
- Parameters have ranges, labels, groups, and safe defaults.
- Orders handle asynchronous and partial states.
- Every acquired resource has a cleanup path.
- Logs are useful, bounded, and secret-free.
- The full custom project compiles.
- The extension passes its runtime verification matrix.