Core concepts
Core concepts
The mental model behind every chapter: how plugins load, what you implement, how data is indexed, and the few patterns that recur across indicators, strategies, bar types, providers and the rest. Read this once and the per-kind chapters become quick reference.
On this page
The plugin model
A plugin is one .NET assembly compiled against public contracts. You implement a base class or an
interface, build a net10.0 DLL, and drop it into the host's Plugins folder.
On launch the host scans that folder, inspects each type, and registers anything that implements a
known contract into the matching catalog — the Add-Indicator dialog, the strategy list, the bar
type picker, the connection factory list, and so on.
There is no manifest, no registration call, and no host config to edit. Discovery is by type: the host finds your plugin by the contracts it implements, not by a name you declare anywhere. This is why the only hard rule is that the host must be able to construct your type — it instantiates your class before applying any settings.
The contract assemblies
TradeStrike.Sdk brings in the three assemblies most plugins need; two more cover lower-level
integration points. Each declares several namespaces — the folder name and the
namespace often differ, so always using the namespace the type actually
declares (the per-kind chapters and the reference spell these
out).
| Assembly | What it holds |
|---|---|
TradeStrike.Pipeline.Contracts |
The bulk of the surface: indicators (TradeStrike.Pipeline.Indicators), plots
(TradeStrike.Pipeline.Plots), series (TradeStrike.Pipeline.Series), bars
(TradeStrike.Pipeline.Bars), market depth (TradeStrike.Pipeline.MarketDepth),
drawing tools (TradeStrike.Pipeline.Drawing) and data providers
(TradeStrike.Pipeline.Providers / TradeStrike.Pipeline.Runtime). |
TradeStrike.Pipeline.Trading.Contracts |
Orders, positions, accounts, risk, commissions and ATM bracket types
(TradeStrike.Pipeline.Trading and sub-namespaces). The trading-provider and risk contracts
live here. |
TradeStrike.Pipeline.Strategies.Contracts |
The Strategy base class, its IStrategyContext runtime context, the
strategy metadata attribute and backtest/analytics types
(TradeStrike.Pipeline.Strategies). |
TradeStrike.Pipeline.Brokerage.Contracts |
Brokerage gateway interfaces (TradeStrike.Pipeline.Brokerage) — a lower-level seam
than a trading provider, splitting trading, account and data gateways. Reference it directly when
building a gateway. |
TradeStrike.Pipeline.TradeHistory.Contracts |
The trade-history store and backfill contracts plus the PersistedFill record
(TradeStrike.Pipeline.TradeHistory). Reference it directly for trade-history plugins. |
Base class or interface, plus optional capability interfaces
This is the single most important pattern in the SDK, and it recurs in every chapter. You implement
one primary contract to be a plugin of a given kind — subclass
IndicatorBase, subclass Strategy, subclass DrawingToolBase,
implement IDataProviderFactory, implement ITradingProvider. Then you opt into
extra abilities by also implementing small "capability" interfaces. The host detects
them with a type check (is IFoo) and lights up the corresponding feature only when present.
The base behaviour stays minimal, and you pay for nothing you don't use. A few concrete examples drawn straight from the contracts:
| Primary contract | Optional capability interface | What it adds |
|---|---|---|
IndicatorBase |
IChartCustomRender |
Paint custom chart decorations beyond standard plot series (footprint cells, profiles, price lines). |
IndicatorBase |
IIndicatorPanelHint |
Declare a default panel placement (price overlay vs. its own subpanel). |
IndicatorBase |
IIndicatorToolbarMenu |
Contribute items to the chart toolbar menu. |
IndicatorBase |
IRepaintNotifier |
Request a coalesced repaint when visual state changes off the bar cadence. |
| a data provider | IMarketByOrderFeed |
Expose a live Market-By-Order (Level-3) feed; the host uses it only if you implement it. |
| a data provider | IContinuousBarsCapableProvider |
Advertise that the provider can serve continuous historical time bars. |
Here is the shape in code — one indicator that is also a custom renderer. The lifecycle methods
come from IndicatorBase; OnCustomRender comes from the capability interface.
VolumeProfile.csusing TradeStrike.Pipeline.Indicators;
// Primary contract: IndicatorBase makes it an indicator.
// Capability interface: IChartCustomRender opts into the chart paint pass.
public sealed class VolumeProfile : IndicatorBase, IChartCustomRender
{
public override void OnBarUpdate(IIndicatorContext ctx)
{
// ... accumulate per-price volume from ctx.Bars(0) ...
}
// Only called because the host saw `is IChartCustomRender`.
public void OnCustomRender(IIndicatorRenderContext ctx)
{
// ... draw the histogram into ctx.PlotArea ...
}
// Background decoration: candles draw on top.
public CustomRenderLayer Layer => CustomRenderLayer.BelowBars;
}
Series & bars-ago indexing
Bars and indicator outputs are exposed as series with bars-ago indexing,
the same convention NinjaTrader users know: [0] is the most recent value,
[1] the one before, and so on. For a bar series, [0] is the current bar
(in-progress when live, most-recent-closed in history).
C#var bars = ctx.Bars(0); // the primary IBarSeries
Bar latest = bars[0]; // most recent bar
double prevClose = bars[1].Close; // the bar before it
int n = bars.Count; // total bars ever observed (monotonic)
Two interfaces carry this. IBarSeries is the read-only OHLCV series with bars-ago
indexing, a Count (total bars ever observed) and a Capacity (the ring size,
the largest lookback it can serve). It also exposes cached price views — Close,
High, Median, Typical and so on — each an
ISeries<double>. ISeries<T> is the generic read-only series used
for indicator output plots.
For your own writable output, the SDK provides ChunkedSeries<double> in
TradeStrike.Pipeline.Series: an append-only, single-writer / multi-reader series. You append
one value per bar and hand the series to a plot.
double-typed series, positions that aren't computed
yet hold double.NaN — that is how warmup bars render as gaps rather than a misleading
zero. Use TryGet for a safe read during warmup instead of indexing into an unpopulated
slot. Code that distinguishes "not computed" from "computed as zero" must check for NaN.
Ambient providers
Some plugins need a shared host service that the framework can't pass in through a constructor — because the host instantiates your type generically, before any context exists. The SDK solves this with ambient providers: a small static accessor in the contracts that the host sets once at startup, and that your plugin reads on demand. It is a deliberately narrow seam — the host injects an implementation; you consume an interface.
Examples that exist in the contract assemblies (consume them; the host owns the wiring):
-
IndicatorResolverAmbient(TradeStrike.Pipeline.Indicators) — exposes the host's indicator catalog (built-ins and plugin-supplied) so an indicator that hosts other indicators can discover and create them by type. -
TradingHoursAmbient(TradeStrike.Pipeline.Indicators.TradingHours) — resolves trading-hours templates so session-aware logic can ask the host for the right session windows. -
ContractRolloverAmbient(TradeStrike.Pipeline.Indicators.ContractRollover) — resolves futures contract-rollover dates for an instrument.
The shape is the same across all of them: a static class with a Resolver-style property
the host sets, plus a scoped Use(...) helper for tests. Conceptually, think of an ambient
provider as "a singleton service the host hands you, reachable without a constructor argument." You
generally read these; setting them is the host's job (and the test path's).
IIndicatorContext,
IStrategyContext — get shared data from it first (it already surfaces trading hours,
tick size, the indicator resolver, and more). Reach for an ambient provider only when no context is in
scope at the point you need the service.
Lifecycle phases
Every plugin kind that processes market data follows the same three phases, even though the method names differ per kind. Understanding the phases — not memorising names — is what matters.
-
Init. The host has constructed your object and applied settings; validate parameters and
allocate buffers. (Indicators:
OnInit. Strategies:OnInitialize/OnStart.) -
History / data loaded. Backfill has arrived; compute over the whole history in one pass,
oldest bar first. (Indicators:
OnDataLoaded.) -
Live updates. New data flows in; update incrementally so live values exactly match what the
history pass produced. (Indicators:
OnBarUpdateper closed bar, plus optionalOnMarketDataper tick andOnMarketDepth/OnMarketByOrderfor depth. Strategies:OnBar,OnFill,OnOrderUpdate,OnPositionUpdate.)
The flow below is the indicator lifecycle; strategies, bar builders and providers follow the same init → history → live arc.
flowchart TD
A[Host constructs your type] --> B[Apply settings / parameters]
B --> C[Init: validate & allocate]
C --> D[History: compute over backfill, oldest first]
D --> E{Live data}
E -->|bar closes| F[Per-bar update]
E -->|tick / depth| G[Per-tick / depth update]
F --> E
G --> E
E -->|teardown| H[Dispose / stop]
Threading expectations
At a high level: lifecycle callbacks for one instance are delivered in order — you are not called re-entrantly for the same object — so per-instance state needs no locking on the hot path. Series follow a single-writer / multi-reader model: your plugin writes its output; the chart and other readers read it, possibly from another thread. Hot-path callbacks (per-tick processing, custom render, which can fire up to 60×/second) should be allocation-free and fast; do slow or blocking work off the callback. Where a contract is explicitly thread-safe or has a specific delivery model, its chapter and the reference say so — follow those notes rather than assuming.
Discovery & deployment at a glance
Build a net10.0 DLL, copy only that DLL into the host's Plugins
folder (the SDK's MSBuild target can do this on every build), and restart. The loader scans the folder
once at startup, finds your types by the contracts they implement, and registers them. The
Building & deploying chapter covers the folder choices,
dependency isolation and troubleshooting in full.