Chart trader panel
Chart trader panel INTERFACE
Give your indicator its own tab on the chart trader panel, for the controls and readouts that need more room than a toolbar dropdown.
On this page
What it is
Implement IChartPanelProvider and your indicator contributes its own tab to the
chart trader panel, beside Trade and the AI assistants. It is the room a toolbar dropdown
does not have: a scanner's controls and its findings, a calibration block, a live readout of
whatever your indicator computes. You describe the controls; the host renders them natively,
so there is no UI framework anywhere in your plugin.
Reach for the tab when the user needs to SEE something while they work, or when the controls belong together as a panel. For a couple of quick toggles, the chart toolbar menu is the lighter surface.
When the panel appears
An indicator instance lives on exactly one chart tab, so the rule is simple: the panel is built when the indicator is added to the chart and released when it is removed. Two charts each carrying your indicator get two independent panels, each driven by its own instance. The host does the bookkeeping; the indicator stays oblivious.
Building the panel
LiquidityScanner.cs (chart trader tab)using SabrTrader.Pipeline.Indicators;
using SabrTrader.Pipeline.Panels;
public sealed class LiquidityScanner : IndicatorBase, IChartPanelProvider
{
private ChartPanelLabel _walls;
private ChartPanelLabel _lastPull;
public ChartPanel CreateChartPanel()
{
_walls = new ChartPanelLabel("Walls", "0");
_lastPull = new ChartPanelLabel("Last pull", "-");
return new ChartPanel("Liquidity",
new ChartPanelGroup("Detection",
new ChartPanelNumber("Min size", v => MinSize = (int)v,
value: MinSize, minimum: 1, maximum: 10_000, step: 10),
new ChartPanelCheckBox("Track pulls", on => TrackPulls = on, isChecked: true)),
new ChartPanelSeparator(),
_walls,
_lastPull,
new ChartPanelButton("Clear", ClearFindings));
}
public void OnChartPanelReleased(ChartPanel panel) => _walls = null;
public override void OnBarUpdate(IIndicatorContext ctx)
{
if (_walls is null) return; // no panel on this chart
_walls.Text = _found.Count.ToString();
_walls.Accent = _found.Count > 0 ? ChartPanelAccent.Warning : ChartPanelAccent.Neutral;
}
}
Return null from CreateChartPanel to contribute no tab, for a
configuration where the panel would be pointless.
The element kinds
| Element | What it is |
|---|---|
ChartPanelButton |
A push button. Its callback runs when the user presses it. |
ChartPanelToggle |
A two-state button that stays pressed while it is on: Run / Pause, Long only, Arm. |
ChartPanelCheckBox |
A labelled checkbox, for an option the user ticks rather than a button they press. |
ChartPanelChoice |
One of a fixed set of options: Both / Long only / Short only, One-shot / Continuous. |
ChartPanelNumber |
A numeric field. The host clamps and rounds every edit to your declared range before
your callback sees it, and the mouse wheel steps it by your step. |
ChartPanelText |
A single-line text field. You get the raw string and may write a normalised value back. |
ChartPanelLabel |
A read-only caption and value. This is where a live status belongs. |
ChartPanelRow |
Lays its children side by side on one line, sharing the width evenly. |
ChartPanelGroup |
A captioned block of elements, for when a panel grows past a handful of controls. |
ChartPanelSeparator |
A horizontal rule between two stretches of the panel. |
Every element carries IsEnabled, IsVisible and an
Accent. The accent is a semantic role, not a colour:
Neutral, Positive, Negative, Warning,
Info. The host resolves it against whichever theme the user is running, so a
panel keeps matching the platform when they switch theme and you never hardcode a brush.
Keeping it live
The elements are live objects, not a snapshot you re-emit. Write to them whenever you have something new and the tab follows:
updating the panel_status.Text = position is null ? "Flat" : $"{position.Quantity:+0;-0} @ {position.AveragePrice}";
_status.Accent = pnl >= 0 ? ChartPanelAccent.Positive : ChartPanelAccent.Negative;
_flatten.IsEnabled = position is not null;
Nothing is rebuilt when you do that. The structure of a panel is fixed the moment you construct it, and only the value you changed is re-read, so the user never loses focus, a scroll position or a half-typed value because an update landed while they were working.
Updating on every tick is fine. A burst of writes collapses into one repaint showing the newest value, so a status readout costs nothing per tick and a fast market cannot flood the UI.
Writing a value from your own code never raises your own callback. That is what lets you reflect a state you changed yourself, a strategy pausing on a risk stop for example, without re-entering the handler that would have made the change.
Threading
Element callbacks run on the chart UI thread, the same thread as your toolbar menu commands and your custom rendering, so you can share plain fields between all three without locks or volatile.
Writing to elements works from anywhere, including OnBarUpdate on a background
calculation pass. The host marshals and coalesces for you.
A handler that throws is contained: the failure is logged against your indicator and the rest of the chart carries on. It will not take the chart window with it, and it will not disappear silently either.
You have the full picture
That completes the indicator chapter. You can build the whole spectrum, from a one-line SMA
to a footprint tool with orderflow data, custom rendering, coalesced repaints, a toolbar
dropdown, a context menu and its own chart-trader tab, entirely against the public contracts
in SabrTrader.Pipeline.Indicators, SabrTrader.Pipeline.Panels,
SabrTrader.Pipeline.Plots, SabrTrader.Pipeline.Series and friends.
For a member-level reference, see
Reference › Indicators.
A strategy contributes a tab through the same contract, with one difference worth knowing: it only gets one once the user attaches it to a chart, and its callbacks run on the strategy thread rather than the UI thread. See Strategies › Chart trader panel. Next up: strategies, which extend the same indicator lifecycle with order and position-state hooks.