SmartDOM columns
Note
Requires ATAS 8.0.15 or later: ATAS 7.0.9 has no SmartDOM column API.
A SmartDOM column is a vertical strip of the SmartDOM module: one cell per price row, under a header. Besides the built-in columns (Price, Bid/Ask, Volume, Trades and so on) the SmartDOM can show columns from your own assembly. Such a column:
- draws its header and one cell per visible price row with the regular drawing API (Drawing);
- receives the trades and the order book changes of the SmartDOM instrument;
- reads the order book, the best prices, the visible price range, the fonts and colors of the SmartDOM through a read-only context;
- has settings: public properties shown in the column settings and saved with the workspace, exactly like indicator settings.
A column cannot place, modify or cancel orders and has no access to accounts or positions: it is a visualization of market data. SmartDOM columns work the same way in the classic ATAS and in ATAS X.
What you build
A public class derived from SmartDomColumn with a public parameterless constructor,
marked with [SmartDomColumn(id)]: the stable column id (see below) and, optionally, DisplayName, the name in the
list of available columns and the default header. OnDrawCell is the only required override.
Lifecycle
| Method | Called |
|---|---|
| constructor | when the column is added or a saved layout is opened (and the tariff allows custom columns); the saved settings are applied right after it |
OnInitialize() |
when the column is attached to a SmartDOM; Context is available from here on |
OnSettingsChanged() |
after OnInitialize and every time the user changes a setting |
OnInstrumentChanged() |
after the SmartDOM switched to another instrument, before the first data of the new one: reset accumulated data here; data of the previous instrument that was not delivered yet is discarded |
OnNewTrades(trades) |
new trades (MarketDataArg, DataType == Trade), oldest first |
OnMarketDepthChanged(levels) |
changed order book levels (DataType is Bid or Ask; volume 0 removes a level) |
OnBestBidAskChanged(bestPrice) |
the best bid or ask changed |
OnTimer() |
about ten times per second while the SmartDOM is visible |
OnDrawColumn(context, bounds) |
once per frame, before the cells; bounds is the whole column, header included |
OnDrawCell(context, cell) |
once per visible row of every frame |
OnDrawHeader(context, bounds, text) |
once per frame; the default draws text (the header set by the user) like the built-in columns |
OnCellClick(cell, e) |
the user clicked a cell; return true if you handled it (a handled right click opens no context menu) |
OnDispose() |
the platform stops using the instance: the column was removed or its layout replaced, the SmartDOM was closed (also on a workspace switch or exit), the column was stopped by an error, the account or the tariff no longer allows it, its settings were replaced as a whole (template), or the DLL was reloaded. Called exactly once for every instance whose constructor completed, even if OnInitialize was not reached or threw; release timers, files and your own subscriptions here. No other method is called afterwards; an exception here is logged and ignored |
SmartDomCell gives the row Price, the cell Bounds and IsMouseOver. Before OnDrawCell the platform fills the
cell with the column background (the Background setting every column has), so you draw only the content.
Threading
The calls of all the extension kinds side by side: Call model.
- The platform never calls one column concurrently: data callbacks, the timer, the settings, drawing and clicks are serialized. Your fields need no locks.
- Data callbacks are not called on the data feed thread: the platform copies the data and delivers it in arrival order shortly afterwards (before the next frame or on the next timer tick). The lists passed to you belong to the call: do not keep them.
- Keep
OnDrawCellcheap: it runs for every row of every frame on the drawing thread. Do heavy work in the data callbacks orOnTimerand only look up prepared values when drawing. - Delivery is time-sliced: each delivery hands the column only the batches that were waiting when it started, and
stops after about 4 ms; the rest follows on the next frame or timer tick. A column that is slower than its data
falls behind, and when too much data is waiting (or while the SmartDOM panel is hidden and not drawn) newer data
is skipped. Re-read the current state from
Contextinstead of relying on a complete history. - Time limit. A single call (or the drawing of one frame of the column) that takes longer than 100 ms is written to the log once. If that happens 5 times within 10 seconds, the platform stops the column like a column that threw an exception (see Errors).
The context
Context (ISmartDomColumnContext) is a read-only view of the SmartDOM:
| Member | Meaning |
|---|---|
Instrument, TickSize |
the instrument the SmartDOM displays (IInstrumentInfo: name, exchange, tick size, and TimeZone / TimeZoneOffset, the time zone set for the instrument in the platform; null while none is selected). For cross-trading and synthetic instruments this is the displayed instrument whose book and trades you receive, not the one orders go to |
Scale, RowPriceStep |
ticks per row and the price step between rows |
VisibleHigh, VisibleLow, RowHeight |
the rows of the last frame |
GetBidVolume(price), GetAskVolume(price) |
the volume of the order book level shown in a row (0 if none) |
GetDepth(isAsk) |
a snapshot of all bid or ask levels |
BestBid, BestAsk, LastTrade |
the top of the book and the last trade (null while unknown) |
CumulativeBids, CumulativeAsks, MaxBidVolume, MaxAskVolume |
totals of the shown order book |
HighOfDay, LowOfDay |
the current day range |
Font, BoldFont, HeaderFont, TextFormat |
the fonts and the text alignment chosen for the SmartDOM |
BackgroundColor, HeaderBackgroundColor, HeaderTextColor |
the standard colors |
FormatPrice(price), FormatVolume(volume) |
the SmartDOM formatting of prices and volumes |
GetY(price), GetPrice(y) |
conversions between rows and coordinates: GetY returns the top Y of the row in the last drawn frame, or null if the row is not visible |
Redraw() |
asks for a new frame (for example, after OnTimer changed something) |
Settings
Public properties with a getter and a setter are the column settings (Settings). They
are shown in the column settings of the SmartDOM below the common column settings (header, width, colors) and saved
in the workspace and SmartDOM templates. Settings must be of concrete serializable types (numbers, strings, bool,
enums, colors, filters); a setting that cannot be restored is skipped and keeps its default value. The values are
saved as ordinary JSON objects, so a nested settings class may use constructor parameters, read-only properties or
public fields.
Settings are read and written by the settings window under the same lock as your other methods, with one exception:
a nested settings object (for example a filter such as FilterInt) is edited in place by its editor on the UI
thread, while your column may be running. The platform notices the change (the object must implement
INotifyPropertyChanged, as the filters do) and calls OnSettingsChanged afterwards, serialized as usual. So read
nested settings only in OnSettingsChanged and copy the values you need into your own fields; use those fields in
the data and drawing methods.
Errors
If one of your methods throws (or the column repeatedly exceeds the time limit), the platform writes the reason to
the log, releases the instance (OnDispose) and shows «Error» in its header; the rest of the SmartDOM keeps
working. A new instance with the saved settings starts when the user changes one of the column settings (header,
width, colors), or when the layout is loaded again (workspace or SmartDOM template). Do not rely on this as a control
flow: check your inputs instead.
Example
The column shows, for every row, the bid volume minus the ask volume of the order book levels within a few rows around it: a bar in the color of the dominating side and the value.
using System;
using System.ComponentModel.DataAnnotations;
using ATAS.Indicators;
using ATAS.Indicators.SmartDom;
using OFT.Rendering.Context;
using Color = System.Windows.Media.Color;
using Colors = System.Windows.Media.Colors;
namespace ATAS.Docs.Samples.SmartDom;
[SmartDomColumn("mycompany.bid-ask-imbalance", DisplayName = "Bid/Ask imbalance")]
public sealed class BidAskImbalanceColumn : SmartDomColumn
{
private decimal _scaleMax; // largest |imbalance| of the previous frame: scales the bars
private decimal _frameMax; // largest |imbalance| of the frame being drawn
[Display(Name = "Rows around the price", GroupName = "Calculation", Order = 10)]
[Range(0, 20)]
public int Depth { get; set; } = 2;
[Display(Name = "Bid dominance", GroupName = "Colors", Order = 20)]
public Color BidColor { get; set; } = Color.FromArgb(160, 0, 150, 70);
[Display(Name = "Ask dominance", GroupName = "Colors", Order = 30)]
public Color AskColor { get; set; } = Color.FromArgb(160, 200, 40, 40);
[Display(Name = "Text", GroupName = "Colors", Order = 40)]
public Color TextColor { get; set; } = Colors.Black;
[Display(Name = "Show values", GroupName = "Colors", Order = 50)]
public bool ShowValues { get; set; } = true;
protected override void OnSettingsChanged()
{
if (Depth < 0)
Depth = 0;
}
protected override void OnInstrumentChanged()
{
_scaleMax = 0;
_frameMax = 0;
}
protected override void OnDrawColumn(RenderContext context, System.Drawing.Rectangle bounds)
{
// Called once per frame before the cells.
_scaleMax = _frameMax;
_frameMax = 0;
}
protected override void OnDrawCell(RenderContext context, SmartDomCell cell)
{
var imbalance = 0m;
for (var row = -Depth; row <= Depth; row++)
{
var price = cell.Price + row * Context.RowPriceStep;
imbalance += Context.GetBidVolume(price) - Context.GetAskVolume(price);
}
if (imbalance == 0)
return;
var size = Math.Abs(imbalance);
_frameMax = Math.Max(_frameMax, size);
var share = _scaleMax > 0 ? Math.Min(1m, size / _scaleMax) : 1m;
var bar = new System.Drawing.Rectangle(cell.Bounds.X, cell.Bounds.Y, (int)(cell.Bounds.Width * share), cell.Bounds.Height);
context.FillRectangle((imbalance > 0 ? BidColor : AskColor).Convert(), bar);
if (ShowValues)
context.DrawString(Context.FormatVolume(imbalance), Context.Font, TextColor.Convert(), cell.Bounds, Context.TextFormat);
}
}
The colors are System.Windows.Media.Color settings, as in chart indicators; Convert() from ATAS.Indicators
turns them into the System.Drawing.Color the drawing API expects. This Convert() exists only in the assemblies of
the classic ATAS, so build such a column for net10.0-windows; the same DLL runs in ATAS X, where the platform
converts these types automatically (ATAS X and macOS).
The column id
The argument of [SmartDomColumn] is the column id: the platform saves the column in workspaces and SmartDOM
templates under this id, not under the class name.
- Use the form
<vendor>.<column-name>in lowercase letters, digits and dashes, for examplemycompany.bid-ask-imbalance; the vendor part should identify you. - The vendor
atasis reserved: an id starting withatas.is rejected when the assembly is loaded, with the reason in the platform log. - Never change the id of a released column: saved layouts would lose it. Renaming the class or the namespace is safe.
- Two columns cannot share an id; if they do, only one of them is listed.
Problems found while loading (an invalid, reserved or duplicate id, a missing public parameterless constructor) are written to the platform log, and the column is not listed.
Installing
The DLL goes into the Extensions or Indicators folder, like a chart indicator, and is reloaded the same way
(Project setup). The columns appear in
the SmartDOM settings, Columns tab, in the Custom group of the available columns. After a reload, open the
columns settings again to see a new column; columns already in a SmartDOM switch to the new build and keep their
settings. If the DLL is removed, saved layouts keep the column: it is shown empty with «Not available» in the header
and comes back with its settings when the DLL is installed again.
To sell a column, mark its class with [FeatureId] (Distribution): the column is
offered only to accounts that have the feature, and a column saved in a layout of an account without it stays empty
with «Not available» in the header (its constructor is not called).
Tariff restriction
Custom SmartDOM columns can be restricted by the tariff (Tariffs): your columns are still listed, but adding one shows the tariff banner, and the columns already in a layout are shown empty with «Not available» in the header and are not created or called. The tariff is checked on every frame, so the columns start working as soon as the restriction is lifted, no need to add them again.