Loading...
Searching...
No Matches
Heatmap indicators

Overview

A heatmap indicator draws on top of the ATAS Heatmap module (and in its sub-panels). It is a different kind of indicator from the chart indicators described in the previous articles:

  • it does not work with bars — it reacts to individual trades, market-profile snapshots, timer ticks and, optionally, market-by-order (MBO) data;
  • every instance runs on its own background task supervised by the platform; the platform never calls the data and lifecycle methods of one instance concurrently, so they need no locks for the indicator's own fields — with one exception, IHeatmapProfileConsumer.GetRequiredProfilePeriods (see «Threading» below);
  • the indicator owns a long-lived visual state and writes samples into it under a short exclusive lease; the renderer reads the state at frame rate without waiting for the indicator.

Heatmap indicators work the same way in the classic ATAS and in ATAS X.

Building a heatmap indicator in your own assembly

Create a class library exactly as for a chart indicator (see «Development of a user indicator»). Besides ATAS.Indicators.dll, reference OFT.Rendering.dll (visual descriptors, trade ticks) and ATAS.DataFeedsCore.dll (MBO records) from the platform folder, with <Private>false</Private>.

A heatmap indicator consists of:

  • a class derived from HeatmapIndicator<TSettings> marked with the [HeatmapIndicator(typeId)] attribute and a public parameterless constructor;
  • a descriptor built once in the static constructor with Describe<TSelf>(): the visuals (price line, value area, level lines, sub-panel scalar or pair, histogram) and their series; the returned handles are kept in static readonly fields;
  • a settings class with a public parameterless constructor and a Version property (the platform only checks that the property exists and saves its value with the other settings; compare it in your code to recognise settings saved by an older build); its public properties are shown in the indicator settings and are available through the inherited Settings property;
  • one or more capability interfaces — the platform only sends the data the indicator asks for:
Interface Implement when
IHeatmapTradeTickConsumer you react to trades (ProcessTicksAsync) — most indicators do
IHeatmapProfileConsumer you react to market-profile snapshots (POC, value area, OHLC)
IHeatmapMboConsumer you aggregate MBO data — icebergs, annotated trades (ProcessMboAsync)
IHeatmapWarmupIndicator you want history before the live data starts
IHeatmapTimerConsumer you need periodic calls independent of the data
IHeatmapHistoricalDataLoadedConsumer you rebuild when the user scrolls into older history
IHeatmapDisposableIndicator you hold resources that must be released

Rules worth remembering:

  • Write to the state only inside using var lease = State.BeginUpdate(); in a data callback. Disposing the lease publishes the changes; a second lease while one is open throws.
  • Append expects non-decreasing timestamps; samples out of order are dropped.
  • Do not keep HeatmapTickBatch or its spans after the call returns — the buffer is reused.
  • Keep each call cheap (O(number of new ticks), not O(history)). The cancellationToken of a call is cancelled after 2 seconds (30 seconds for WarmUpAsync); a call that then ends with OperationCanceledException counts as a timeout, and 3 timeouts within 30 seconds stop the indicator. A call that is still running when the same time has passed once more after the cancellation, or that throws any other exception, stops the indicator at once.
  • OnStateResetCoreAsync is called when the platform cleared the state (for example, the instrument changed): reset your accumulators there and do not write to the state.

Threading

The platform calls ConfigureAsync, the data callbacks (ProcessTicksAsync, ProcessProfileAsync, ProcessMboAsync, …), the timer, warm-up and reset methods one at a time, on the instance's own task: fields used only by them need no locks. ConfigureAsync comes first, with the saved settings, and again after every change of the settings; the first call precedes the first OnStateResetCoreAsync, so Context and Runtime are still null in it.

The exception is IHeatmapProfileConsumer.GetRequiredProfilePeriods. The platform's profile pump calls it from its own thread at any time, concurrently with the instance's task. It must be thread-safe, cheap and non-blocking:

  • return an immutable snapshot that is already published — compute the set in ConfigureAsync (or another serialized method) and store it in a volatile field, replacing the whole array, never modifying it in place;
  • do not compute anything from the indicator's working fields there, do not take locks, do not change the indicator's state.
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using OFT.Rendering.Heatmap;
public sealed class PocLevelsSettings
{
public int Version { get; set; } = 1;
public bool ShowWeeklyPoc { get; set; }
}
[HeatmapIndicator("mycompany.poc-levels", DisplayName = "POC levels")]
public sealed class PocLevelsIndicator
: HeatmapIndicator<PocLevelsSettings>
{
// Read by the profile pump from another thread: replaced as a whole, never changed in place.
private volatile HeatmapProfilePeriod[] _requiredPeriods = { HeatmapProfilePeriod.CurrentDay };
// ... descriptor and drawing as in the example below ...
public override ValueTask ConfigureAsync(PocLevelsSettings settings, CancellationToken cancellationToken)
{
// Serialized with the data callbacks; publishes a new immutable set for the pump.
_requiredPeriods = settings.ShowWeeklyPoc
? new[] { HeatmapProfilePeriod.CurrentDay, HeatmapProfilePeriod.CurrentWeek }
: new[] { HeatmapProfilePeriod.CurrentDay };
return ValueTask.CompletedTask;
}
// May run concurrently with every other method: only returns the published snapshot.
public IReadOnlyCollection<HeatmapProfilePeriod> GetRequiredProfilePeriods() => _requiredPeriods;
public ValueTask ProcessProfileAsync(HeatmapMarketProfileSnapshot profile, CancellationToken cancellationToken)
{
// Serialized: the indicator's own fields and the state lease are safe to use here.
return ValueTask.CompletedTask;
}
}
Author-facing entry points for the heatmap indicator API. The non-generic HeatmapIndicator coexists w...
Definition IHeatmapIndicator.cs:113
Definition IHeatmapIndicator.cs:523
Definition HeatmapIndicator.cs:1

An empty set means the instance needs no profile data now; the platform then requests no profiles for it.

Example

The indicator below draws a VWAP line from trades and, when MBO data is available, the number of tracked icebergs in a sub-panel:

using System.Threading;
using System.Threading.Tasks;
using OFT.Rendering.Heatmap;
using OFT.Rendering.Heatmap.Mbo;
namespace MyCompany.HeatmapIndicators;
public sealed class VwapIcebergsSettings
{
public int Version { get; set; } = 1;
public bool ShowIcebergCount { get; set; } = true;
}
[HeatmapIndicator("mycompany.vwap-icebergs", DisplayName = "VWAP + Icebergs")]
public sealed class VwapIcebergsIndicator
: HeatmapIndicator<VwapIcebergsSettings>
{
private static readonly HeatmapIndicatorDescriptor _descriptor;
private static readonly HeatmapIndicatorSeriesHandle<decimal> _vwap;
private static readonly HeatmapIndicatorSeriesHandle<decimal> _icebergs;
static VwapIcebergsIndicator()
{
var build = Describe<VwapIcebergsIndicator>();
_vwap = build.PriceLine("vwap.line", "VWAP")
.Series("vwap.price", HeatmapIndicatorSeriesRole.Price, HeatmapIndicatorValueKind.Price);
_icebergs = build.SubPanelScalar("icebergs.panel", "Icebergs")
.Series("icebergs.count", HeatmapIndicatorSeriesRole.Scalar, HeatmapIndicatorValueKind.Integer);
_descriptor = build.Done();
}
private decimal _priceVolume;
private decimal _volume;
public override HeatmapIndicatorDescriptor Descriptor => _descriptor;
public ValueTask ProcessTicksAsync(HeatmapTickBatch ticks, CancellationToken cancellationToken)
{
using var lease = State.BeginUpdate();
var vwap = lease.Series(_vwap);
foreach (var tick in ticks)
{
_priceVolume += tick.Price * tick.Volume;
_volume += tick.Volume;
if (_volume > 0)
vwap.Append(tick.TimestampNanos, _priceVolume / _volume);
}
return ValueTask.CompletedTask;
}
// Not called at all while MBO data is unavailable to the indicator (no MBO feed for the
// instrument, or the tariff does not include MBO data for third-party indicators):
// the VWAP line keeps working, the iceberg panel just stays empty.
public ValueTask ProcessMboAsync(HeatmapMboDataSnapshot snapshot, CancellationToken cancellationToken)
{
if (!Settings.ShowIcebergCount || snapshot.Status.Kind != HeatmapMboStatusKind.Ok)
return ValueTask.CompletedTask;
using var lease = State.BeginUpdate();
lease.Series(_icebergs).Append(snapshot.LiveEdgeNanos, snapshot.Icebergs.Count);
return ValueTask.CompletedTask;
}
protected override ValueTask OnStateResetCoreAsync(
CancellationToken cancellationToken)
{
// The platform cleared the visual state (e.g. the instrument changed): drop the accumulators.
_priceVolume = 0;
_volume = 0;
return ValueTask.CompletedTask;
}
}
Strongly typed handle for a series within a visual. Returned from HeatmapIndicatorVisualHandle....
Definition HeatmapIndicatorSeriesHandle.cs:17
Immutable raw MBO store snapshot shared by all indicator instances. Records are detached from the pro...
Definition IHeatmapMboConsumer.cs:51
IReadOnlyList< MboIcebergRecord > Icebergs
Definition IHeatmapMboConsumer.cs:87
HeatmapMboStatus Status
Definition IHeatmapMboConsumer.cs:89
long LiveEdgeNanos
Definition IHeatmapMboConsumer.cs:91
Live runtime context exposed to a heatmap indicator. Unlike the v1 HeatmapIndicatorContext record (sn...
Definition IHeatmapIndicatorContext.cs:35
Runtime handle the platform passes to indicators at warm-up / reset time. Lets the indicator drive it...
Definition IHeatmapIndicator.cs:417
Optional capability for indicators that aggregate Market-By-Order analytics. The platform supplies on...
Definition IHeatmapMboConsumer.cs:19
Definition IHeatmapIndicator.cs:492
@ Settings
The settings button (ATAS and ATAS X). Ignored while the module has settings: the user always reaches...

The TypeId

The first argument of [HeatmapIndicator] is the TypeId: the stable identifier under which the platform saves the indicator in heatmap templates and workspaces. Rules:

  • the TypeId must have the form <vendor>.<indicator-name>: lowercase Latin letters, digits and dashes, each part starting with a letter, exactly one dot — for example mycompany.vwap-icebergs; the vendor part should identify you (company or author name). An indicator from your assembly with another TypeId (upper case, no dot, underscores, …) is rejected when the assembly is loaded, and the reason is written to the platform log. SmartDOM columns, Watchlist columns and custom modules use the same rule for their ids;
  • the vendors heatmap and atas are reserved for the built-in indicators: an indicator from your assembly whose TypeId starts with heatmap. or atas. (in any letter case) is rejected when the assembly is loaded, and the reason is written to the platform log;
  • never change the TypeId of a released indicator — saved templates would lose it; to change the settings format, keep the TypeId, increase the Version of the settings class and convert older settings in your own code;
  • two indicators cannot share a TypeId; if they do, the one loaded later is rejected.

Installing the indicator

Put the DLL into the Extensions folder or into the Indicators folder next to it, where chart indicators are installed (AppData%\ATAS\Extensions or AppData%\ATAS\Indicators for the classic ATAS, AppData%\ATAS X\Extensions or AppData%\ATAS X\Indicators for ATAS X; the platform reads both), or distribute it through the marketplace — one DLL may contain chart indicators and heatmap indicators together.

The platform watches both folders. ATAS X loads a new or replaced DLL by itself about a second after the files stop changing; the classic ATAS shows a reload button in the status bar and loads the libraries when the user presses it (an AI agent uses the ATAS Connect tool extensions_reload). A file that could not be loaded is named with the reason and does not stop the other files — see Extending %ATAS, «Reloading a library». After the libraries are reloaded, open the heatmap indicators window again — the new build of the indicator is listed there, and instances already on the heatmap switch to it the next time the indicators are applied (or when the heatmap is reopened). Problems found while loading (an invalid descriptor, a missing parameterless constructor, an invalid, reserved or duplicate TypeId) are written to the platform log, and the indicator is not listed.

MBO data

Implement IHeatmapMboConsumer to receive MBO analytics: the platform keeps one MBO subscription per instrument and sends every consumer the same immutable snapshot (HeatmapMboDataSnapshot) — annotated trades, tracked icebergs, the data status and the live edge. Each instance applies its own filters; do not modify the snapshot. Check snapshot.Status.Kind before using the data: MBO is available only for instruments and connections that provide it.

MBO data for third-party indicators can also be restricted by the tariff (see «Developer API and tariffs»). In that case ProcessMboAsync is not called and Context.MboDataSource is null. Design the indicator so that its other parts keep working without MBO data, as in the example above.

Related articles