Table of Contents

Performance

The recommendations below apply to any chart indicator, in the classic ATAS and in ATAS X alike, and to chart strategies: a strategy is an indicator too and is calculated in the same way.

Calculation: OnCalculate

The platform calls OnCalculate for every historical bar and, on the current bar, on every tick, so the code inside it must be as light as possible:

  • Avoid allocations in OnCalculate. Do not create collections, strings, LINQ queries, lambdas or temporary objects on every call, and avoid boxing. Keep reusable buffers and objects in fields and initialize them in the constructor or in OnInitialize.
  • Create data series once. Create ValueDataSeries and the other series in the constructor and add them to DataSeries; do not recreate them in OnCalculate. Write the result by bar index (series[bar] = value).
  • Cache the candle. Call GetCandle(bar) once and keep the result in a local variable instead of requesting the same candle repeatedly for the same bar. Remember the valid index range 0..CurrentBar-1.
  • Footprint data is heavier than bar data. Iterating all price levels of a candle on every tick is expensive: do it only when necessary and cache intermediate results where possible. GetAllPriceLevels() creates a new object for every price level; the overload that takes a PriceVolumeInfo writes every level into the passed object instead, so keep that object in a field, read its values inside the loop and do not store references to it:
using ATAS.Indicators;

namespace ATAS.Docs.Samples.ChartIndicators;

public class PriceLevelsCacheIndicator : Indicator
{
    private readonly PriceVolumeInfo _level = new();

    protected override void OnCalculate(int bar, decimal value)
    {
        var candle = GetCandle(bar);
        var maxVolume = 0m;

        // Every price level is written into the same _level object:
        // read its values inside the loop and do not store references to it.
        foreach (var level in candle.GetAllPriceLevels(_level))
        {
            if (level.Volume > maxVolume)
                maxVolume = level.Volume;
        }

        this[bar] = maxVolume;
    }
}

Rendering: OnRender

If the indicator draws in OnRender, that is a hot path too:

  • The call frequency of OnRender depends on the layouts you subscribe to with SubscribeToDrawingEvents. The Final layout is redrawn on every chart repaint, including mouse movement; LatestBar on every tick; Historical on a new candle, scrolling and zooming. Subscribe only to the layouts you need: an unnecessary subscription to Final forces drawing on every cursor move.
  • Draw only what is visible. Iterate the visible bars, from FirstVisibleBarNumber to LastVisibleBarNumber, not the whole history; get the coordinates with ChartInfo.GetXByBar and ChartInfo.GetYByPrice.
  • Do not compute in OnRender. Perform the heavy calculations in OnCalculate and store the result; leave only the drawing itself in OnRender. Reuse pens and fonts (RenderPen, RenderFont) instead of creating them on every frame.
  • Call RedrawChart sparingly, only when the picture actually needs updating; calling it on every tick produces redundant repaints.

Threads

The platform calls your code on its own threads, not on the UI thread. Follow this contract instead of relying on internal thread details (the order of the calls and their threads are in Lifecycle and threads):

  • Do UI work only through DoActionInGuiThread. Anything that must run on the interface thread goes through this method, never directly from a calculation or data callback.
  • Market-data callbacks arrive on the data-feed thread: OnNewTrade, OnCumulativeTrade and MarketDepthChanged.
  • Do not assume different callbacks share a thread. If you keep mutable state in fields and access it from more than one callback, guard it, or keep the writes in OnCalculate. Never write to DataSeries from your own background threads.
  • OnRender only draws. Keep the computation in OnCalculate and store the result.

ATAS X

If the indicator must work in ATAS X, do not use custom WPF editors or other Windows-only UI components: use the attributes ([Display] and the others) for the settings instead of hand-written WPF controls. See Project setup for the types ATAS X converts.