Building & deploying

Building & deploying

Overview & discovery

What a plugin DLL is, how the TradeStrike host finds it on launch, which catalog each type lands in, and the build → deploy → restart loop you repeat as you develop.

What a plugin is

A plugin is a single .NET assembly — one .dll — that references the TradeStrike.Sdk contract package and contains one or more public types implementing a TradeStrike extension contract. There is no manifest, no registration call, and no entry-point method to write. You compile against the contracts, drop the DLL in the host's Plugins folder, and the host finds your types on its next launch.

A plugin is not limited to one kind of extension. A single assembly can carry any mix of indicators, strategies, bar-type builders, data-provider factories and Market Analyzer columns at once, so you can ship a whole feature — an indicator plus the custom bar type it depends on, say — as one file. There is no upper limit and no requirement that the types be related.

The build, deploy, restart loop

Plugin discovery happens once, at startup. The host scans the Plugins folder, loads each DLL, and registers what it finds into its catalogs — then never re-scans. Your inner development loop is therefore three steps:

  1. Build your plugin project (dotnet build, or build in your IDE).
  2. Deploy the freshly built DLL into the host's Plugins folder — either by the SDK's opt-in auto-deploy target or a manual copy (see Where to deploy).
  3. Restart TradeStrike. The new build is picked up on the next launch.
There is no hot reload. Editing and rebuilding a plugin while the host is running changes nothing on screen — the assembly already loaded stays loaded for the lifetime of the process. You must fully restart TradeStrike to load a new build. If "my changes don't take effect" is your symptom, this is almost always why.

What gets discovered

The loader entry point is PluginLoader.ScanDirectory. For each DLL it produces a PluginCatalog that buckets the types it found by the contract each one implements. You never write a line of wiring code — discovery is purely by the interfaces a type implements. Implement the right contract and the type appears; implement two and it appears in two catalogs. The table below maps "contract I implemented" to "catalog I land in", with a link to the chapter that explains how to build each kind.

Implement this… …and you land in Chapter
IIndicator (usually via IndicatorBase) Indicators Indicators
IStrategy (or the pipeline Strategy base, see note) Strategies Strategies
ITickBarBuilder TickBarBuilders Bar types
IDerivedBarBuilder DerivedBarBuilders Bar types
BarSpecification (subclass) BarSpecifications Bar types
IDataProviderFactory DataProviderFactories Connections
IColumnDefinition MarketAnalyzerColumns Market Analyzer
DrawingToolBase + [DrawingToolMetadata] drawing-tool catalog (scanned from loaded assemblies) Drawing tools
Strategies and drawing tools are scanned a little differently. The pipeline Strategy base class is a plain class, not an IIndicator, so the per-DLL PluginCatalog lists strategies separately and the host's StrategyCatalog.BuildDefault() — which scans every loaded assembly — surfaces them. Because the installer loads your plugin assembly first, your strategies are found automatically. Drawing tools are likewise picked up by the DrawingToolCatalog scanning loaded assemblies for [DrawingToolMetadata]. In every case the rule is the same: drop the DLL, restart, and they appear.

Discovery rules — make your type loadable

Before a type is registered, the loader checks it against a short list of requirements. Every rule must pass; a type that fails even one is skipped silently rather than crashing the load. The overwhelming majority of "my plugin doesn't show up" reports come down to one of these, so it is worth knowing them.

  • Public — not internal, not nested-private. (A public nested type is fine.)
  • Concrete — not abstract, not an interface, not an open generic type definition.
  • Zero-arg constructable — the host constructs instances generically before applying parameter values, so the type must have a public constructor callable with no arguments: either a real parameterless () ctor or one whose every parameter has a default value. This is the single most common reason a type "doesn't show up".
  • Implements a recognised contract from the table above.
"All-optional" counts as zero-arg. A constructor like MyIndicator(IIndicator? parent = null, ISeries<double>? input = null) is callable with no arguments, so it passes — this is exactly the convention the built-in indicators use. A constructor with even one required parameter (MyIndicator(int period)) fails the check and the type is dropped.

The example below puts the rules side by side. The first type satisfies all of them and is discovered; the next two each break exactly one rule and are silently ignored.

C# — good vs badusing TradeStrike.Pipeline.Indicators;

// Discovered: public, sealed (concrete), zero-arg ctor, implements IIndicator via IndicatorBase.
public sealed class MyIndicator : IndicatorBase
{
    public MyIndicator() { }
}

// NOT discovered: the only ctor needs a required argument, so it is not zero-arg constructable.
public sealed class Broken : IndicatorBase
{
    public Broken(int period) { }
}

// NOT discovered: internal type.
internal sealed class Hidden : IndicatorBase { }
Parameters still work. Needing a zero-arg constructor does not mean no inputs. You expose tunables as properties tagged [IndicatorParameter] (indicators) or declared with IntParameter() / DoubleParameter() (strategies); the host sets them after construction. See the Indicators and Strategies parameter chapters.