Discovery & metadata
Discovery & metadata
How the host finds your strategy class without any registration step, and how
[StrategyMetadata] controls the name and description users see in the catalog.
Discovery & metadata
There is no manifest to edit and no Register(…) call to make. At load time the host
scans every loaded assembly for launchable Strategy subclasses and adds each one to the
catalog. A type is launchable when it is a public, non-abstract Strategy
subclass with a public parameterless constructor — the catalog must be able to
new one per run. In practice, dropping your plugin DLL into the plugins folder is the entire
deployment step.
Discovery alone gives the catalog a class name (humanized — SmaCrossoverStrategy
becomes “Sma Crossover”, dropping a trailing Strategy), which is rarely what you
want a trader to read. Attach the [StrategyMetadata] attribute to supply a friendly display
name and a one-line description; both are shown in the Strategy Analyzer and the Control Center launch
form. The attribute is optional but strongly recommended for anything you ship.
C#using TradeStrike.Pipeline.Strategies;
using TradeStrike.Pipeline.Strategies.Catalog;
[StrategyMetadata(DisplayName = "Donchian Breakout", Description = "Breaks an N-bar high/low.")]
public sealed class BreakoutStrategy : Strategy { /* ... */ }
OnInitialize
— not in the constructor — so it runs at the right point in the lifecycle. (The protected
helpers throw if touched from the constructor: the run context is not bound yet.)How parameters are discovered
Discovery is more than a type scan. For each launchable type the catalog constructs a throwaway probe
instance and runs its declare phase (OnInitialize) once to capture the parameters it
registers — that is how a launch form knows your knobs and their optimization ranges without running
the strategy. Discovery is defensive: an assembly that fails to load, a constructor that throws, or an
OnInitialize that throws never breaks the catalog — the bad type is skipped, or included
with whatever it managed to declare before the throw.
The catalog API
The collection the host builds from that scan is a StrategyCatalog (namespace
TradeStrike.Pipeline.Strategies.Catalog). You rarely build one yourself — the host owns the
live catalog — but it is the public surface for tooling and tests.
StrategyCatalog.csStrategyCatalog cat = StrategyCatalog.BuildDefault(); // scan loaded assemblies
// or: StrategyCatalog.BuildFrom(new[] { typeof(MyStrategy).Assembly });
foreach (StrategyDescriptor d in cat.Strategies)
Console.WriteLine($"{d.DisplayName}: {d.Description}");
StrategyDescriptor? sd = cat.Find("Donchian Breakout"); // by id, display name, type name, or full name
Strategy instance = sd!.CreateInstance(); // a fresh, ready-to-run instance
A StrategyDescriptor exposes a stable Id (the type's full name for a compiled
strategy — it survives a display-name change), DisplayName, Description,
StrategyType, the declared Parameters
(IReadOnlyList<StrategyParameter>), and CreateInstance(). Find
resolves by id, display name, type name, or full type name (case-insensitive).
Dynamic (config-backed) strategies
Beyond reflective discovery, the catalog can hold dynamic strategies registered at runtime —
the mechanism behind config- or AI-authored strategies that run through the exact same path as compiled
ones. RegisterDynamic upserts an entry by a stable id; if you do not supply the parameter
list it is probed from the factory the same way reflective discovery probes a type. Dynamic registration
rejects a display name already used by a compiled strategy (an authored config must not shadow a
built-in), and raises Changed so a bound UI refreshes without a restart.
C#cat.Changed += () => RefreshStrategyList(); // re-read cat.Strategies on any dynamic change
cat.RegisterDynamic(
id: "cfg:donchian-v2",
strategyType: typeof(BreakoutStrategy),
displayName: "Donchian v2 (config)",
description: "Authored breakout.",
factory: () => new BreakoutStrategy());
cat.ClearDynamic(); // host re-registers the current set after a config-store change
cat.Strategies merges the reflective built-ins
and the dynamic set, sorted by display name. It returns a fresh snapshot only when dynamics are present, so
enumerating it is cheap in the common (no-dynamics) case.