Discovery & metadata

Strategies

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 { /* ... */ }
Constructor rule. Because the host instantiates your strategy itself for every run, the class must remain newable with no arguments. Do all per-run setup in 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
One snapshot per 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.