Registration & discovery
Registration & discovery
You don't register a column by hand. The host discovers your definition type the same way it discovers indicators, drawing tools and bar builders — by scanning a dropped-in plugin DLL. This chapter explains how that discovery works, how the catalog resolves a saved column id back into a live definition, and what you must satisfy for it all to succeed.
On this page
Attribute-based discovery
A column participates in discovery by being a public, concrete IColumnDefinition with a
zero-arg-callable constructor — the same convention every other plugin kind uses. The host scans your
DLL, finds the type, and registers it. The [MarketAnalyzerColumn] attribute is not required for
discovery itself; it enriches how the column is presented in the picker (display name, category, description).
Without it the column still loads, but appears under its Header with an empty category.
MidPriceColumn.csusing TradeStrike.Pipeline.MarketAnalyzer;
namespace MyPlugin.Columns;
// Public, concrete, parameterless ctor + the attribute = discovered and categorized.
[MarketAnalyzerColumn("Mid price",
Category = "My plugin",
Description = "(Bid + Ask) / 2, live from the venue's quote stream.")]
public sealed class MidPriceColumn : IColumnDefinition
{
public string Id => "myplugin.midprice";
public string Header => "Mid";
public ColumnDataType DataType => ColumnDataType.Price;
public IColumnValueSource CreateSource(InstrumentRef instrument, IColumnContext context)
=> new MidPriceSource(instrument, context);
}
TradeStrike.Pipeline.Contracts SDK,
implement the contract interface, drop the DLL where the host scans. See
Building & deploying for the shared discovery pipeline.The catalog: resolving ids
A workspace snapshot stores a column by its Id string, never by type. On restore (and when the
Columns dialog is opened) the host turns that id back into a live IColumnDefinition through an
IColumnDefinitionCatalog. The UI and the restore flow both code against this interface, never
against any concrete registry — that is what lets the host chain built-ins, your plugin columns and future
column kinds behind one seam.
IColumnDefinitionCatalog.csnamespace TradeStrike.Pipeline.MarketAnalyzer;
public interface IColumnDefinitionCatalog
{
// Resolve a column id back into its definition, or null when this
// catalog doesn't know the id. A chained catalog delegates further;
// an id no catalog knows (e.g. a column type a newer build shipped)
// is dropped rather than failing the whole workspace.
IColumnDefinition? Resolve(string columnId);
// All ids this catalog can resolve, for the column-picker dialog.
IReadOnlyList KnownIds { get; }
// Display metadata for one known id. Default: derived from the
// resolved definition (header as display name, empty category /
// description) so catalogs that predate descriptors keep working.
ColumnDescriptor? Describe(string columnId) { /* default impl */ }
}
You rarely implement this interface yourself — the host's plugin registry already exposes your column
through a catalog and merges it with the built-in catalog. Implement IColumnDefinitionCatalog
directly only when you ship a family of columns generated at runtime (for example, one column per item
in an external schema) rather than a fixed set of attributed classes.
Id must be treated as a permanent contract.ColumnDescriptor & the picker
The Columns dialog's "Available" list is populated from ColumnDescriptors — pure display
metadata for one column id. When you supply [MarketAnalyzerColumn], the host builds the descriptor
from your attribute; otherwise it derives a minimal one from the definition.
ColumnDescriptor.csnamespace TradeStrike.Pipeline.MarketAnalyzer;
public sealed record ColumnDescriptor(
string Id, // the stable column id
string DisplayName, // name shown in the Available list
string Category, // picker grouping ("Market data", "Session", ...)
string Description, // one-line tooltip / detail text
ColumnDataType DataType, // the cell payload type
bool HasSettings = false, // true when the column exposes [ColumnParameter]s
bool IsEditable = false); // true when the user can type into the cell (e.g. Notes)
The descriptor fields map straight to what the dialog shows:
| Field | Sourced from | Shown as |
|---|---|---|
DisplayName |
[MarketAnalyzerColumn] display name |
The entry's label in "Available". |
Category |
attribute Category
|
The grouping header the entry sits under. |
Description |
attribute Description
|
Tooltip / detail text. |
HasSettings |
presence of [ColumnParameter] properties |
Drives whether a Properties pane appears for the entry. |
IsEditable |
your column kind | Whether the user can type into cells (mirrors NinjaTrader's editable-column capability). |
Deployment
- Create a .NET class library that references the
TradeStrike.Pipeline.ContractsSDK package. - Add your
IColumnDefinitionclasses (and their sources), each public, concrete and parameterless-constructable, decorated with[MarketAnalyzerColumn]. - Build the DLL and place it in the host's plugin folder (the same location your indicators and drawing tools deploy to — see Where to deploy).
- Restart the host. Your columns appear in the Market Analyzer's Columns… dialog, categorized and searchable, and resolve on workspace restore.
What the host enforces (fail-loud at install)
The plugin loader validates your column when it loads the DLL — not silently at workspace restore — so mistakes surface immediately:
-
Public, non-abstract class implementing
IColumnDefinitionwith a public parameterless constructor. -
Idnon-blank and globally unique. A collision with another plugin or a built-in refuses registration with a descriptive error. -
Every
[ColumnParameter]property has a public getter and setter and one of the supported types (bool,int,long,double,decimal,string,TimeSpan,DateTime, enums). Anything else fails at install.
What the host does for you
Discovery
Finds your definition types, registers them, and merges them with the built-ins through a composite catalog — so your column appears in the Columns dialog and resolves on restore.
Persistence
Stores your column Id plus every
[ColumnParameter] value in the workspace and re-applies them on restore. A malformed saved value
skips just that parameter (your default survives) and warns in the activity log.
Styling
Header override, colors, visibility and decimal places from the Columns dialog apply to your column exactly like a built-in. You do nothing.
Conditions & sort
Numeric cells join conditional formatting, alerts and
sorting automatically — publishing a CellValue.Ready(double) from a numeric
DataType is all it takes.
That completes the Market Analyzer column chapter. To put cell values to work — firing on a threshold, routing to a channel — continue to Alerts & channels.