Clone our Wealth-Lab 8 Extension Demo project on GitHub to get a head start in developing your own Extensions!

Strategy Gene Extension API

A Strategy Gene Extension defines reusable genetic components for the WealthLab 9 Strategy Genetic Evolver. The Evolver uses Strategy Genes to randomly construct and mutate trading Strategies over successive generations. A Gene typically represents one of the following:

  • An Entry
  • An Exit
  • A Condition

Each Gene ultimately produces a corresponding Building Block that becomes part of the evolved Strategy.

Build Environment

You can create a Strategy Gene in a .NET development tool such as Visual Studio 2026. Create a class library project that targets .NET10, then reference the WealthLab.Core library DLL that you'll find in the WL9 installation folder.

Your Strategy Gene will be a class in this library that descends from StrategyGeneBase, which is defined in the WealthLab.Core library, in the WealthLab.Backtest namespace. After you implement and build your library, simply copy the resulting assembly DLL into the WL9 installation folder. The next time WL9 starts up, it will discover your Strategy Gene, making it available in appropriate locations of the WL9 user interface.

Visual Studio 2026 Build Environment

Accessing the Host (WL9) Environment

The IHost interface provides access to the current WealthLab environment. Extensions can use it to retrieve application-level information and services, such as the location of the user's WealthLab data folder or the DataSets defined by the user.

You can access the current IHost instance from anywhere in your extension through the WLHost singleton and its Instance property. For example, the following code retrieves the path to the user's WealthLab data folder:

string folder = WLHost.Instance.DataFolder;

Use WLHost.Instance whenever your extension needs access to functionality exposed by the IHost interface.

Gene Name and Code

Name

public abstract string Name

Override this property to return a descriptive name for the Gene.

ShortCode

public abstract string ShortCode

Return a short string that identifies the Gene. The Strategy Evolver displays a Genetic Strategy's Genes as a concatenation of their ShortCode values. For example:

public override string ShortCode => "E";

Keep ShortCode values concise so evolved Strategy representations remain readable.

Gene Parameters

A Strategy Gene can use ordinary .NET properties to store the values that determine its behavior. For example:

public double Value { get; set; }
public int Lookback { get; set; }

GeneData

public virtual string GeneData

Override this property to serialize and deserialize the Gene's state. WealthLab uses GeneData to persist the Gene's parameter values. The getter should encode all relevant Gene properties into a single string, and the setter should restore those values. For example:

public override string GeneData
{
    get
    {
        return
            Value.ToString(
                CultureInfo.InvariantCulture) +
            ";" +
            Lookback.ToString();
    }

    set
    {
        string[] tokens =
            value.Split(';');

        Value =
            Double.Parse(
                tokens[0],
                CultureInfo.InvariantCulture);

        Lookback =
            Int32.Parse(tokens[1]);
    }
}

Use culture-independent formatting for numeric values so saved Gene data is portable across systems using different regional number formats.

Converting a Gene to a Building Block

public abstract BuildingBlockBase GetBuildingBlock()

Each Strategy Gene ultimately produces one BuildingBlockBase instance that becomes part of the evolved Strategy. The Building Block can represent an Entry, Exit, or Condition. Override GetBuildingBlock to:

  1. Create the appropriate Building Block.
  2. Assign its Parameters from the Gene's properties.
  3. Return the configured Building Block.

You can use a built-in WealthLab Building Block or one supplied by your own extension. A common extension therefore contains both:

  • A StrategyGeneBase-derived Gene.
  • A corresponding BuildingBlockBase-derived Building Block.

For example, an Exit Gene might create and configure a SellAfterNBars Building Block:

public override BuildingBlockBase GetBuildingBlock()
{
    SellAfterNBars bb =
        new SellAfterNBars();

    bb.Parameters[0].Value =
        NumBars;

    AddConditionsBlocks(bb);

    return bb;
}

Initialization, Randomization, and Mutation

Initialize

public virtual void Initialize()

WealthLab calls this method when the Gene is initialized as part of a new Genetic Strategy. Override it to perform any required initialization.

Randomize

public virtual void Randomize()

Override this method to assign randomized values to the Gene's properties. Randomize should produce a valid, substantially new configuration for the Gene.

Mutate

public virtual void Mutate(
    GeneticStrategy gs)

Override this method to mutate the Gene. Mutation should normally make a relatively small change to one or a few of the Gene's properties rather than completely replacing its configuration. If the Gene represents an Entry or Exit, call the base implementation so that any child Condition Genes can also mutate:

base.Mutate(gs);

RNG

protected internal static Random RNG

Provides a shared Random instance that Genes can use for randomization and mutation. For example:

if (RNG.NextDouble() > 0.5)
{
    // Select one variation.
}

Mutation Example

A Gene containing a NumBars property might mutate it using:

public override void Mutate(
    GeneticStrategy gs)
{
    NumBars =
        RandomizeValue(NumBars);
}

Entry and Exit Genes

IsEntry and IsExit

public virtual bool IsEntry
public virtual bool IsExit

Override one of these properties and return true to identify the Gene as an Entry or Exit. For example:

public override bool IsExit => true;

IsEntryExit

public bool IsEntryExit

Returns true when either IsEntry or IsExit returns true.

PositionType

public virtual PositionType PositionType

Override this property to indicate the position type handled by the Entry or Exit Gene. Return either:

PositionType.Long

or:

PositionType.Short

AddConditionsBlocks

protected void AddConditionsBlocks(
    BuildingBlockBase bb)

Call this method from GetBuildingBlock for Entry and Exit Genes. It adds the Gene's child Condition Genes to the supplied Building Block. For example:

public override BuildingBlockBase GetBuildingBlock()
{
    SellAfterNBars bb =
        new SellAfterNBars();

    bb.Parameters[0].Value =
        NumBars;

    AddConditionsBlocks(bb);

    return bb;
}

CanAddExit

public virtual bool CanAddExit(
    List<StrategyGeneBase> exits)

Override this method when an Exit Gene should be restricted based on the Exit Genes that have already been added. Return false to prevent the Gene from being added. For example, a Stop Loss Gene might prevent duplicates and ensure that it is not the only Exit:

public override bool CanAddExit(
    List<StrategyGeneBase> exits)
{
    if (exits.Count == 0)
        return false;

    foreach (StrategyGeneBase exit in exits)
    {
        if (exit is SellAtStopLossGene)
            return false;
    }

    return true;
}

Adding Condition Genes to Entries and Exits

protected void InsertConditions(
    int max,
    StrategyGeneBase parentGene,
    StrategyGeneConditionTypes conditionType)

Call this method from an Entry or Exit Gene's Randomize implementation to add randomized Condition Genes. The parameters specify:

  • max - Maximum number of Conditions to add.
  • parentGene - The Entry or Exit Gene that owns the Conditions. Normally pass this.
  • conditionType - The types of Condition Genes that can be selected.

For example:

InsertConditions(
    3,
    this,
    StrategyGeneConditionTypes.Both);

Condition Genes

IsCondition

public bool IsCondition

If neither IsEntry nor IsExit returns true, WealthLab considers the Gene to be a Condition Gene and IsCondition returns true.

ConditionType

public virtual StrategyGeneConditionTypes ConditionType

Override this property to identify the role of a Condition Gene. Available values include:

  • Signal - A primary trading signal, such as a moving average crossover or oscillator entering an oversold region.
  • Filter - A condition used to filter another signal, such as an Indicator being above or below a threshold.
  • Both - The Gene can function as either a Signal or a Filter.

When WealthLab first adds a Condition to an Entry or Exit, it typically selects a Signal Condition. Additional Conditions can then be added as Filters.

CanIncludeCondition

public virtual bool CanIncludeCondition

Override this property when the Gene should only be available under certain circumstances. Return false when it would not currently make sense to include the Condition. For example, a Gene that depends on user-defined chart patterns could return false if no chart patterns currently exist.

Restricting Genes

public virtual bool IsValid(
    GeneticStrategy gs,
    StrategyGeneBase parent)

Override this method to determine whether the Gene is valid for the current Genetic Strategy. The gs parameter contains the Genetic Strategy currently being assembled. For Condition Genes, parent contains the Entry or Exit Gene to which the Condition would be attached. Return false to prevent the Gene from being inserted in the current context. This allows you to enforce relationships between Genes or prevent incompatible combinations.

Working with Indicator Parameters

Many Building Blocks use Indicator Parameters. When configuring one of these Parameters, do not simply assign an IndicatorBase instance to the Parameter's Value. Instead, configure:

Parameter.IndicatorAbbreviation
Parameter.IndicatorParameters

For example:

IndicatorValue bb =
    new IndicatorValue();

Parameter p =
    bb.Parameters[0];

p.IndicatorAbbreviation =
    OscillatorAbbreviation;

p.IndicatorParameters =
    OscillatorParameters;

Selecting Random Indicators

Use IndicatorFactory.Instance to obtain random Indicators suitable for Genetic Strategies. Useful collections include:

FastIndicators

Returns Indicators appropriate for the Evolver while excluding those marked as computationally slow.

IndicatorBase ind =
    IndicatorFactory.Instance
        .FastIndicators
        .RandomValue;

Smoothers

Returns Indicators whose IsSmoother property is true. These typically accept:

Examples include SMA and EMA.

Oscillators

Returns Indicators that define:

  • OverboughtLevel
  • OversoldLevel

For example:

IndicatorBase osc =
    IndicatorFactory.Instance
        .Oscillators
        .RandomValue;

After selecting an Indicator, store its:

ind.Abbreviation

and a copy of its Parameters:

ind.Parameters.Clone()

Always clone Indicator Parameters before modifying them:

IndicatorParameters =
    ind.Parameters.Clone();

RandomizeParameters(
    IndicatorParameters);

This prevents your Gene from modifying the ParameterList belonging to the IndicatorFactory's shared Indicator instance.

Coordinating Genes

Genes can coordinate with one another while a Genetic Strategy is being assembled.

ParentStrategy

public GeneticStrategy ParentStrategy

Returns the Genetic Strategy currently being constructed. Useful members of GeneticStrategy include:

  • Cache - A Dictionary<string, object> for sharing information between Genes.
  • EntryExits - Entry and Exit Genes already added to the Strategy.
  • Entries - Entry Genes already added.
  • GetExits(StrategyGeneBase entry) - Returns Exit Genes associated with a particular Entry.
  • MostRecentEntryExit - Most recently added Entry or Exit Gene.
  • MostRecentEntry - Most recently added Entry Gene.

ParentGene

public StrategyGeneBase ParentGene

For Condition Genes, returns the Entry or Exit Gene that owns the Condition.

ChildGenes

public List<StrategyGeneBase> ChildGenes

For Entry and Exit Genes, returns the Condition Genes already attached to the Gene.

Example: Coordinating Entry and Exit Oscillators

A Gene can use ParentStrategy.Cache to coordinate parameters between separately generated Genes. For example, an oscillator Entry Condition can store itself:

if (ParentGene.IsEntry)
{
    ParentStrategy.Cache[Name] =
        this;
}

A later Exit Condition can retrieve it and use the same oscillator with opposite logic. A simplified version of that pattern is:

public override void Randomize()
{
    if (RNG.NextDouble() > 0.5 &&
        ParentGene.IsExit)
    {
        StrategyGeneBase recentEntry =
            ParentStrategy.MostRecentEntry;

        if (recentEntry != null &&
            ParentStrategy.Cache.ContainsKey(Name))
        {
            OscillatorGene entryOscGene =
                (OscillatorGene)
                    ParentStrategy.Cache[Name];

            OscillatorAbbreviation =
                entryOscGene
                    .OscillatorAbbreviation;

            OscillatorParameters =
                entryOscGene
                    .OscillatorParameters
                    .Clone();

            AboveOrBelow =
                entryOscGene.AboveOrBelow ==
                    AboveBelow.Above
                    ? AboveBelow.Below
                    : AboveBelow.Above;

            IndicatorBase ind =
                IndicatorFactory.Instance.Find(
                    OscillatorAbbreviation);

            Value =
                ind.ReverseTargetValue(
                    entryOscGene.Value);

            return;
        }
    }

    IndicatorBase osc =
        IndicatorFactory.Instance
            .Oscillators
            .RandomValue;

    OscillatorAbbreviation =
        osc.Abbreviation;

    OscillatorParameters =
        osc.Parameters.Clone();

    RandomizeParameters(
        OscillatorParameters);

    AboveOrBelow =
        RNG.NextDouble() > 0.5
            ? AboveBelow.Above
            : AboveBelow.Below;

    if (ParentGene.IsEntry)
    {
        ParentStrategy.Cache[Name] =
            this;
    }
}

This makes it possible for evolved Strategies to develop coordinated Entry and Exit logic rather than choosing every Gene independently.

Converting Building Blocks Back to Genes

public virtual StrategyGeneBase ConvertGeneFromBB(
    ConditionBuildingBlock bb)

WealthLab can create Genetic Strategies from Building Block Strategies supplied to the Strategy Evolver. A Building Block can expose a corresponding Gene through its GetGene method. If a Gene is available, WealthLab then calls ConvertGeneFromBB so that the Gene can be populated using the Building Block's current Parameter values. To support this workflow:

  1. Implement a corresponding custom Building Block.
  2. Override the Building Block's GetGene method to return your Gene.
  3. Override ConvertGeneFromBB in the Gene.
  4. Read the Building Block's Parameters and assign them to the Gene's properties.
  5. Return the populated Gene.

Return null when the supplied Building Block cannot be converted into a valid instance of your Gene.

Helper Methods

RandomizeValue for double

public static double RandomizeValue(
    double value,
    double pct = 10.0)

Returns a randomized value based on value. The pct parameter specifies the approximate maximum percentage variation. For example:

Value =
    RandomizeValue(
        Value,
        20.0);

RandomizeValue for int

public static int RandomizeValue(
    int value)

Returns a randomized integer based on the supplied value, with approximately a 10% variation.

RandomizeParameters

public static void RandomizeParameters(
    ParameterList pl)

Randomizes supported Parameters in the supplied ParameterList. Supported Parameter types include:

  • PriceComponent
  • Int32
  • Double
  • StringChoice

This is particularly useful after cloning an Indicator's Parameters:

IndicatorParameters =
    ind.Parameters.Clone();

RandomizeParameters(
    IndicatorParameters);

RandomPriceComponent

public static PriceComponent RandomPriceComponent

Returns a randomized PriceComponent. Possible values include standard OHLC components and supported averaged PriceComponents. The selection is weighted, with Close receiving the highest probability.

RandomIndexSymbol

public string RandomIndexSymbol

Returns a randomly selected symbol appropriate for use as a market index or secondary symbol. This can be useful when generating Genes that compare the primary symbol with external market data.

Complete Example: PriceCompareIndicatorGene

The following Gene represents a Condition that compares one Price Component with another Price Component or with an Indicator that plots in the Price Pane. It demonstrates:

  • Gene properties
  • Randomization
  • Mutation
  • Indicator selection
  • Indicator Parameter cloning
  • Building Block creation
  • Building Block-to-Gene conversion
using WealthLab.Core;
using WealthLab.Indicators;

namespace WealthLab.Backtest
{
    public class PriceCompareIndicatorGene :
        StrategyGeneBase
    {
        public override string Name =>
            "Price Comparisons";

        public override string ShortCode =>
            "E";

        public override
            StrategyGeneConditionTypes ConditionType =>
            StrategyGeneConditionTypes.Filter;

        public PriceComponent Price
        {
            get;
            set;
        }

        public PriceComponent PriceCompareTo
        {
            get;
            set;
        }

        public AboveBelow AboveOrBelow
        {
            get;
            set;
        }

        public string IndicatorAbbreviation
        {
            get;
            set;
        } = "";

        public ParameterList IndicatorParameters
        {
            get;
            set;
        } = new ParameterList();

        public int BarsAgo
        {
            get
            {
                return _barsAgo;
            }

            set
            {
                if (value < 0)
                    value = 0;

                _barsAgo = value;
            }
        }

        public override void Randomize()
        {
            Price =
                RandomPriceComponent;

            AboveOrBelow =
                RNG.NextDouble() > 0.5
                    ? AboveBelow.Above
                    : AboveBelow.Below;

            PriceCompareTo =
                RandomPriceComponent;

            while (PriceCompareTo == Price)
            {
                PriceCompareTo =
                    RandomPriceComponent;
            }

            if (RNG.NextDouble() > 0.5)
            {
                IndicatorBase ind =
                    IndicatorFactory.Instance
                        .FastIndicators
                        .RandomValue;

                while (ind.PaneTag != "Price")
                {
                    ind =
                        IndicatorFactory.Instance
                            .FastIndicators
                            .RandomValue;
                }

                IndicatorAbbreviation =
                    ind.Abbreviation;

                IndicatorParameters =
                    ind.Parameters.Clone();

                RandomizeParameters(
                    IndicatorParameters);
            }

            BarsAgo =
                RNG.Next(5) + 1;

            if (IndicatorAbbreviation != "")
                BarsAgo--;
        }

        public override void Mutate(
            GeneticStrategy gs)
        {
            int num =
                RNG.Next(4);

            if (num == 0)
            {
                Price =
                    RandomPriceComponent;

                while (Price == PriceCompareTo)
                {
                    Price =
                        RandomPriceComponent;
                }
            }
            else if (num == 1)
            {
                if (IndicatorAbbreviation == "")
                {
                    PriceCompareTo =
                        RandomPriceComponent;

                    while (Price == PriceCompareTo)
                    {
                        PriceCompareTo =
                            RandomPriceComponent;
                    }
                }
                else
                {
                    RandomizeParameters(
                        IndicatorParameters);
                }
            }
            else if (num == 2)
            {
                BarsAgo =
                    RandomizeValue(
                        BarsAgo);
            }
            else
            {
                AboveOrBelow =
                    AboveOrBelow.Opposite();
            }
        }

        public override BuildingBlockBase
            GetBuildingBlock()
        {
            IndicatorCompareIndicator ici =
                new IndicatorCompareIndicator();

            ici.Parameters[0].Value =
                Price.ToString();

            ici.Parameters[0]
                .IndicatorAbbreviation =
                Price.ToString();

            ici.Parameters[1].Value =
                AboveOrBelow
                    .AsLessThanGreaterThan();

            if (IndicatorAbbreviation != "")
            {
                ici.Parameters[2].Value =
                    IndicatorAbbreviation;

                ici.Parameters[2]
                    .IndicatorAbbreviation =
                    IndicatorAbbreviation;

                ici.Parameters[2]
                    .IndicatorParameters =
                    IndicatorParameters;
            }
            else
            {
                ici.Parameters[2].Value =
                    PriceCompareTo.ToString();

                ici.Parameters[2]
                    .IndicatorAbbreviation =
                    PriceCompareTo.ToString();
            }

            ici.Parameters[3]
                .SetNumericValue(
                    BarsAgo);

            return ici;
        }

        public override StrategyGeneBase
            ConvertGeneFromBB(
                ConditionBuildingBlock bb)
        {
            if (bb is IndicatorCompareIndicator)
            {
                IndicatorCompareIndicator ici =
                    bb as IndicatorCompareIndicator;

                if (ici.Qualifier != null)
                    return null;

                if (!PriceComponentExtensions
                    .IsPriceComponent(
                        ici.Parameters[0]
                            .IndicatorAbbreviation))
                {
                    return null;
                }

                PriceCompareIndicatorGene pci =
                    new PriceCompareIndicatorGene();

                pci.Price =
                    PriceComponentExtensions
                        .StringToPriceComponent(
                            ici.Parameters[0]
                                .IndicatorAbbreviation);

                pci.AboveOrBelow =
                    ici.Parameters[1].AsString ==
                        "greater than"
                        ? AboveBelow.Above
                        : AboveBelow.Below;

                if (PriceComponentExtensions
                    .IsPriceComponent(
                        ici.Parameters[2]
                            .IndicatorAbbreviation))
                {
                    pci.PriceCompareTo =
                        PriceComponentExtensions
                            .StringToPriceComponent(
                                ici.Parameters[2]
                                    .IndicatorAbbreviation);
                }
                else
                {
                    pci.IndicatorAbbreviation =
                        ici.Parameters[2]
                            .IndicatorAbbreviation;

                    pci.IndicatorParameters =
                        ici.Parameters[2]
                            .IndicatorParameters;
                }

                pci.BarsAgo =
                    ici.Parameters[3].AsInt;

                return pci;
            }

            return null;
        }

        private int _barsAgo = 1;
    }
}