Watchlist columns
Note
Requires ATAS 8.0.15 or later: ATAS 7.0.9 has no Watchlist column API.
The Watchlist shows one row per instrument and one column per value: the last price, the best bid and ask, the day open, high and low, the volume, the change and so on. Besides the built-in columns it can show columns from your own assembly. Such a column:
- computes the cell of one instrument from the instrument data: trades, best bid and ask changes and the exchange statistics (day open, high, low, volume, previous close, ...);
- publishes the cell as a value: text, an optional number to sort by and optional colors; the platform draws it like the built-in cells, so the column looks the same in the classic ATAS and in ATAS X, and the user can sort by it;
- has settings: public properties saved with the workspace and the Watchlist lists.
A column has no access to orders, accounts or positions and does not draw by itself.
What you build
A public class derived from WatchlistColumn with a public parameterless constructor,
marked with [WatchlistColumn(id)]: the stable column id (see below) and, optionally, DisplayName, the column
header and its name in the column chooser.
One instance per row
The platform creates one instance of your class per Watchlist row and attaches it to the instrument of that row
(Instrument: name, exchange, tick size, and TimeZone / TimeZoneOffset, the time zone set for the instrument in
the platform). Write the column for a single instrument: keep its state in ordinary fields; no dictionaries keyed by
instrument are needed. A Watchlist holds up to 500 instruments, so keep the instance small and its callbacks cheap.
Instances exist only while the column is shown: hiding the column drops them, showing it again creates new ones.
Lifecycle
| Method | Called |
|---|---|
| constructor | when the column is shown (and the account and the tariff allow it), once per row; the saved settings are applied right after it |
OnInitialize() |
when the instance is attached to its row; Instrument is available from here on |
OnSettingsChanged() |
after OnInitialize, and after the user changed the column settings |
OnSummaryChanged(summary) |
right after OnSettingsChanged when the data feed has already reported the exchange statistics of the instrument, and every time they change |
OnNewTrades(trades) |
new trades of the instrument (MarketDataArg, DataType == Trade), oldest first |
OnBestBidAskChanged(bestPrice) |
the best bid or ask changed (DataType is Bid or Ask) |
OnTimer() |
about once per second while the column is shown |
OnDispose() |
the platform stops using the instance: its row or the column was removed, the column was hidden, the Watchlist 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 (a layout or Watchlist list applied), 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 |
InstrumentDailySummary (also used by custom modules) carries the statistics the built-in
columns show: the best prices and volumes, the last trade, the current day (DayOpen, DayHigh, DayLow,
DayVolume, DayTurnover), PreviousDayClose, the last 24 hours (crypto exchanges), OpenInterest,
SettlementPrice. A value is null while the feed has not reported it; which values a feed reports depends on the
exchange.
The cell
Assign Value whenever the cell changes; null shows an empty cell. A
WatchlistCellValue is immutable: create a new one to change the cell.
| Member | Meaning |
|---|---|
Text |
the text shown in the cell |
SortKey |
the number the column is sorted by; a cell with a number is right-aligned like the built-in numeric columns. null sorts the cell by its text and aligns it to the left |
TextColor, BackgroundColor |
System.Drawing.Color of the text and the cell background, or null for the regular Watchlist colors. The background is not drawn while the row is selected |
WatchlistCellValue.FromNumber(value, format) |
a numeric cell: the formatted value, sorted by it |
When the user sorts by your column, empty cells come first in ascending order, then the cells with a number (by the number), then the text-only cells (by the text). The platform reads the published values when it draws and sorts, it never calls your column for that, so drawing and sorting stay fast however many rows there are. Custom columns cannot be used in the Watchlist filter yet.
Threading
The calls of all the extension kinds side by side: Call model.
- The platform never calls one instance concurrently, and all the rows of a column share one lock: your fields need no locks.
- The callbacks are not called on the data feed thread and never while the Watchlist is drawn: the platform copies the data and delivers it shortly afterwards (within about 0.1 s) on a background thread. The lists passed to you belong to the call: do not keep them.
- Data reaches your column only while it is shown; a hidden column costs nothing.
- Trades are delivered in arrival order. The best prices and the exchange statistics are a current state: if they change several times between two deliveries, your column gets only the latest value.
- Delivery is time-sliced: one pass hands your column what was waiting when it started and stops after about 8 ms; the rest follows on the next pass. A column that is slower than its data falls behind, and when too much data is waiting newer trades are skipped.
- Time limit. A single call 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).
Settings
Public properties with a getter and a setter are the column settings (Settings). They
are saved in the workspace and in the user's Watchlist lists, and restored into every row instance right after its
constructor, before OnInitialize. The first time the column is shown, the values your constructor sets are saved as
the column's settings. Settings must be of concrete serializable types (numbers, strings, bool, enums, colors); a
setting that cannot be restored is skipped and keeps its default value.
The user edits them in the Watchlist settings window, Custom columns tab (shown when a custom column with
settings is shown), one group per column. The editor works on the saved values, not on your instances: after a
change every instance gets the new values and OnSettingsChanged, on the processing thread as usual. Keep settings
of simple types: a nested settings object is edited as a copy and its changes are not saved.
The same settings are what an AI agent connected through ATAS Connect sees and changes (the settings scope
watchlistColumn of a custom column): it reads the names of [Display] and the limits of [Range], so describe
your settings with them.
Errors
If one of your methods throws (in any row), or the column repeatedly exceeds the time limit, the platform writes the reason to the log, stops the whole column, every row, and shows «Error» in its header; its cells become empty and the rest of the Watchlist keeps working. The column starts again with new instances when the user hides and shows it again, or when the layout is loaded again (workspace or Watchlist list); the settings of a stopped column are not offered in the settings window until it runs again. Do not rely on this as a control flow: check your inputs instead.
Example
The column shows the spread, the distance between the best ask and the best bid, in ticks, and highlights wide
spreads. The colors are System.Drawing.Color: the cells are drawn by the platform in both ATAS and ATAS X, so the
column needs no WPF types.
using System.ComponentModel.DataAnnotations;
using System.Drawing;
using ATAS.Indicators;
using ATAS.Indicators.Watchlist;
namespace ATAS.Docs.Samples.Watchlist;
[WatchlistColumn("mycompany.spread-ticks", DisplayName = "Spread, ticks")]
public sealed class SpreadInTicksColumn : WatchlistColumn
{
private decimal? _bid;
private decimal? _ask;
[Display(Name = "Wide spread, ticks", Order = 10)]
public int WideSpreadTicks { get; set; } = 3;
[Display(Name = "Wide spread color", Order = 20)]
public Color WideSpreadColor { get; set; } = Color.OrangeRed;
protected override void OnSummaryChanged(InstrumentDailySummary summary)
{
// The exchange statistics carry the best prices too: a value before the first quote arrives.
_bid ??= summary.BestBidPrice;
_ask ??= summary.BestAskPrice;
Update();
}
protected override void OnBestBidAskChanged(MarketDataArg bestPrice)
{
if (bestPrice.DataType == MarketDataType.Bid)
_bid = bestPrice.Price;
else
_ask = bestPrice.Price;
Update();
}
private void Update()
{
if (_bid is not { } bid || _ask is not { } ask || Instrument.TickSize <= 0)
{
Value = null;
return;
}
var ticks = (ask - bid) / Instrument.TickSize;
Value = new WatchlistCellValue(ticks.ToString("0.#"), ticks)
{
TextColor = ticks >= WideSpreadTicks ? WideSpreadColor : null
};
}
}
The column id
The argument of [WatchlistColumn] is the column id: the platform saves the column in workspaces and Watchlist
lists under this id, not under the class name. The rules are the same as for SmartDOM columns: the form
<vendor>.<column-name> in lowercase letters, digits and dashes (mycompany.spread-ticks); the vendor atas is
reserved; never change the id of a released column, renaming the class or the namespace is safe; two Watchlist
columns cannot share an id, but a Watchlist column and a SmartDOM column may. 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). Your columns are
offered in the column chooser of the Watchlist, the list where the built-in columns are shown and hidden, marked
«(Custom)» after the name; check one to show it. After a reload, a new DLL is offered in the open Watchlists without
a restart, and the columns already shown switch to the new build and keep their settings. If the DLL is removed,
saved Watchlist 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 Watchlist columns are restricted together with the custom SmartDOM columns (Tariffs): your columns are still listed in the column chooser, but showing one opens the tariff banner and the column stays hidden; the columns already shown in a saved layout are empty with «Not available» in the header and are not created or called. The tariff is checked continuously, so the columns start working as soon as the restriction is lifted.