Dependencies & isolation

Building & deploying

Dependencies & isolation

Which assemblies the host already provides, when a third-party library belongs next to your plugin and when it must not, and the load-context model that makes "DLL only" the rule for contracts but not for your own dependencies.

What the host provides vs what you ship

The TradeStrike.Sdk package is a meta-package: it bundles the contract assemblies you compile against. The host already loads those assemblies, so you reference them at build time but never deploy them. The split is simple:

Assembly Provided by the host? Deploy with your plugin?
TradeStrike.Pipeline.Contracts Yes No
TradeStrike.Pipeline.Trading.Contracts Yes No
TradeStrike.Pipeline.Strategies.Contracts Yes No
The .NET runtime / framework assemblies Yes No
A NuGet library the host does not provide (CSV parser, exchange SDK, …) No Yes
Your own plugin DLL Yes
Rule of thumb. Anything starting with TradeStrike.Pipeline* → never deploy (the host owns it). Your own private third-party dependencies → deploy alongside the plugin DLL.

Third-party dependencies

The "deploy your DLL only" rule applies to framework and contract assemblies the host already provides. It does not apply to genuine third-party libraries the host knows nothing about.

If your plugin references a NuGet library the host does not provide — a CSV parser, a JSON serializer it doesn't ship, an exchange SDK — that library does belong next to your plugin DLL in the Plugins folder. The plugin's isolated load context resolves such "sidecar" assemblies from its own folder through an AssemblyDependencyResolver built over the plugin's path. Put the sidecar DLL in the same folder and it just works.

The auto-deploy target only copies your primary output. The SDK's DeployToAlgoStudioPlugins target copies $(TargetPath) (your DLL) and its PDB — not your dependency tree. If your plugin has a genuine sidecar dependency, copy that one DLL into the Plugins folder yourself, or add your own copy step. This is deliberate: it stops the target from duplicating host-provided assemblies into the plugin folder, which would break type identity.

The isolation model

Each plugin DLL is loaded into its own collectible AssemblyLoadContext — named Plugin:<filename>. Isolation is deliberate: it lets two plugins reference different versions of the same private library without colliding, and it keeps one plugin's dependency tree from leaking into another's. Here is what the installer runs at startup:

graph TD
      A[Host startup] --> B[PluginInstaller.Install]
      B --> C[PluginLoader.ScanDirectory]
      C --> D[For each DLL: new PluginLoadContext]
      D --> E[Load DLL into its own ALC]
      E --> F[Catalog public types by contract]
      F --> G[Register indicators / columns; strategies and drawing tools picked up from the loaded assembly]
      G --> H[Write summary + errors to the Control Center Log tab]
  1. At startup, PluginInstaller.Install calls PluginLoader.ScanDirectory on the Plugins folder.
  2. Each *.dll is loaded into its own collectible AssemblyLoadContext named Plugin:<filename>.
  3. The loader catalogs the public, zero-arg-constructable types by the contract each implements; indicators (and Market Analyzer columns) are registered into the shared catalogs, and the now-loaded assembly makes its strategies and drawing tools discoverable.
  4. A one-line summary — plus any load or registration errors — is written to the Control Center Log tab.

A missing folder, a malformed DLL, or a single failing type never aborts startup — the failure is captured in the report and the rest of the app continues.

Why contracts must come from the host

Here is the crux of why isolation forces the deployment rule. The host and your plugin must agree on a shared vocabulary of types — IIndicator, IStrategy, and the rest of the contracts. In .NET, type identity is not just the type's name: it is the name plus the exact assembly instance it was loaded from. If a contract assembly were resolved inside the plugin's isolated context from a copy you shipped, the runtime would hold two distinct IIndicator types — the host's and yours — that look identical but are not interchangeable.

The plugin load context prevents this by refusing to load the contract assembly into the plugin context: when the runtime asks the plugin context for the assembly that defines IIndicator, the context returns nothing and lets resolution fall through to the host's default context. Every other request is resolved against the plugin's own folder — which is exactly why a sidecar dependency in that folder is found, and why a stray copy of a contract DLL would be found instead of the host's, breaking identity.

Type identity is per-assembly-instance. The contract assembly that defines IIndicator is always resolved from the host's context, so typeof(IIndicator) is literally the same type object on both sides. Ship your own copy and the host's assignability check sees your indicator implement a different IIndicator, fails, and skips the plugin. Your private third-party deps have no such shared-identity requirement — which is exactly why they are safe, and expected, to live in the Plugins folder where the isolated context resolves them.

Version conflicts & targeting

Because each plugin gets its own load context, two plugins can ship different versions of the same third-party library and neither poisons the other. There is one constraint you must respect, though: your plugin must target a framework compatible with the host.

  • Target framework. The contract assemblies target net10.0, so your plugin must target net10.0 (or higher within the same major) — the same baseline as the TradeStrike host. A plugin built for an older framework will fail to load.
  • Don't shadow host assemblies. Never ship a different version of a TradeStrike.Pipeline* contract assembly to "pin" a version. You cannot win that fight — the contract is always resolved from the host — and shipping a copy only risks the identity break above.
  • Private deps are yours to version. For genuine third-party libraries, ship whatever version you compiled against; isolation keeps it from clashing with other plugins or the host.