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.

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:
- Create the appropriate Building Block.
- Assign its Parameters from the Gene's properties.
- 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:
- A TimeSeries source.
- An integer period.
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:
- Implement a corresponding custom Building Block.
- Override the Building Block's GetGene method to return your Gene.
- Override ConvertGeneFromBB in the Gene.
- Read the Building Block's Parameters and assign them to the Gene's properties.
- 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;
}
}