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

Advanced Position Sizer API

This document details the API for building Advanced Position Sizers for Wealth-Lab 8. A Position Sizer calculates the number of shares/contracts for a Transaction during the backtest process. It has access to the current and historical equity/cash levels of the simulation, as well as other information in order to make the position sizing decision. Position Sizers are exposed to the user in the WL8 position sizing user interface section, in the Strategy Settings tab of the Strategy window.

Build Environment

You can create an Advanced Position Sizer in a .NET development tool such as Visual Studio 2022. Create a class library project that targets .NET8, then reference the WealthLab.Core library DLL that you'll find in the WL8 installation folder.

Note: If you are using Visual Studio 2022, it will need to be updated to at least version 17.8.6 to use .NET8.

Your Advanced Position Sizer will be a class in this library that descends from PositionSizerBase, 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 WL8 installation folder. The next time WL8 starts up, it will discover your Advanced Position Sizer, making it available in appropriate locations of the WL8 user interface.

Visual Studio 2022 Build Environment

Accessing the Host (WL8) Environment

The IHost interface allows your extension to access information about the user's WealthLab environment. For example, the location of the user data folder, or obtaining a list of DataSets defined by the user. At any point in your extension's code base, you can access an instance of the IHost interface using the singleton class WLHost and its Instance property. Example:

//get user data folder
string folder = WLHost.Instance.DataFolder;

Descriptive Properties

Override the following properties that provide descriptive details about your Position Sizer and describes how it functions.

public abstract string Name

Return a descriptive name for your Position Sizer that will be used throughout WL8.

public virtual string Description

Return a brief description of how your Position Sizer works. This is displayed when the user selects or configures your Position Sizer from the drop down in WL8's Strategy window.

public virtual bool UsesBasisPrice

Return true if your Position Sizer should allow the user to specify the basis price option, either current bar market close or next bar market open, in the position sizing user interface. If you return true here, this option will be visible in the position sizing user interface, otherwise not.

public virtual bool UsesMaxRiskPct

Return true if your Position Sizer calls the Max Risk Percent Sizer so that WealthLab can Automatically issue a Stop Loss order at the RiskStopLevel when that Trading Preference is enabled.

protected virtual void GenerateParameters

If your Position Sizer has Parameters, override this method and create the Parameter instances here, adding them to the Parameters property.

Note: PositionSizerBase descends from the Configurable base class. See the Configurable class reference for more information about parameters.

Sizing Positions

public virtual void Initialize()

WL8 calls this prior to sizing any positions. You can override this method to perform any required one-time initialization.

public abstract double SizePosition(Transaction t, BarHistory bars, int idx, double basisPrice, double equity, double cash)

WL8 calls this method each time it needs to size a position. The t parameter is the instance of the Transaction object that represents the trade (or signal) being processed. Refer to the Transaction reference for relative properties. The bars parameter contains the BarHistory instance being processed, and the idx parameter is the current index into the BarHistory instance. The basisPrice parameter contains the Transaction's basis price, which will be one of the following:

  • for Market orders - the basis price depends on which position size setting was selected by the user, either "current bar's closing price" or "next bar's open price".
  • for Limit/Stop orders - the basis price is the Limit/Stop order price.

The equity and cash parameters return the current simulated account equity and available cash values, respectively.

Your Position Sizer should use the information in the parameters, along with any of the required properties outlined below, to calculate a position size as the return value of the method.

public TimeSeries EquityCurve

A TimeSeries instance that represents the simulated equity curve. This property, as well as the other TimeSeries properties that follow, are updated only to the point in time that this trade signal was made in the simulation.

public TimeSeries CashCurve

A TimeSeries instance that represents the available cash in the simulation.

public TimeSeries DrawdownCurve

A TimeSeries instance that represents the drawdown of the simulation. Drawdown is the largest peak to trough decline in the simulation's equity curve and is calculated on a closing price basis.

public TimeSeries DrawdownPctCurve

A TimeSeries instance that represents the percentage drawdown of the simulation.

public List<Transaction> Orders

A List of Transaction instances that contains the orders that have already been sized and processed on this bar of processing, and includes entries and exits.

public List<Position> OpenPositions

A List of Position objects that contains the positions still open in the simulation.

public List<Position> ClosedPositions

A List of Position objects that contains the positions that have already been closed in the simulation.

public List<Position> Positions

A List of Position objects that contains all positions, open and closed, in the simulation.

public List<Transaction> Candidates

A List of Transaction instances that contains all of the entry signals that are being processed (sized) on this bar of the simulation.

Helper Methods

public double CalculatePositionSize(PositionSizeTypes pst, double value, BarHistory bars, double basisPrice, double equity, int? idx)

This helper method calculates a position size based on the standard types, one of which you specify in the pst parameter. Possible PositionSizeTypes are:

  • Dollar - a fixed of dollar (or base currency) amount, as specified in the value parameter
  • Quantity - a fixed number of shares/contracts, as specified in the value parameter
  • PctOfEquity - a percentage of the current simulated equity, as specified in the value parameter

You'll typically be calling this helper method from within the implementation of the SizePosition method. So, for the idx parameter, pass the value that was passed down as the idx parameter in SizePosition.

public bool UseFuturesMode(BarHistory bars)

Returns true if Futures Mode should be used for the BarHistory instance specified in the bars parameter. If Futures Mode should be used, your Position Sizer can take account of the PointValue, Margin, and TickSize properties available in the BarHistory instance's SymbolInfo property. The most relevant property here is Margin, which determines how much capital should be required to enter a single share/contract of the security.

For example, assume $10,000 of available capital, a Position Sizer might use the following logic to calculate a position size with Futures Model in mind:

if (FuturesMode)
   return 10000 / bars.SymbolInfo.Margin;
else
   return 10000 / basisPrice;

BasicPositionSizer

If your Position Sizer is building atop the standard approach of allowing the user to select a position sizing type (fixed dollar amount, fixed share amount, percent of equity) along with a value, you can derive your Position Sizer from BasicPositionSizer rather than PositionSizerBase. BasicPositionSizer automatically overrides the GenerateParameters method to create two parameters:

  1. Position Size Type - a Parameter of type StringChoice which contains the 3 position sizing options
  2. Amount - a Parameter of type Double which specified the amount

BasicPositionSizer implements Initialize to read the values established by the user for the Parameters, storing them in local variables. It then uses these local variables in its implementation of SizePosition, shown below:

public override double SizePosition(Transaction t, BarHistory bars, int idx, double basisPrice, double equity, double cash)
{
    return CalculatePositionSize(_posSizeType, _amount, bars, basisPrice, equity);
}

Example Position Sizer

Below is the source code for the Max Entries per Bar Position Sizer included in WL8. It derives from BasicPositionSizer, so exposes the standard position sizing options. But it sets the position size to zero if it detects more than the configured number of trades on the same bar (or optionally day) of trading.

using System.Linq;
using WealthLab.Core;

namespace WealthLab.Backtest
{
    //A PosSizer that allows only a maximum number of entries per bar of data
    public class MaxEntriesPerBar : BasicPositionSizer
    {
        //add parameter to control how many entries per day
        public override void GenerateParameters()
        {
            base.GenerateParameters();
            Parameters.Add(new Parameter("Max Entries", ParameterType.Int32, 2, 1.0, 999999999.0));
            Parameters.Add(new Parameter("For intraday trades, sum up positions opened during the day", ParameterType.Boolean, false));
        }

        //Name
        public override string Name => "Max Entries per Bar";

        //description
        public override string Description => "Provides the basic Position Sizing options, with the additional ability to limit the number of entries taken on each bar (or day) of data.";

        //initialize
        public override void Initialize()
        {
            base.Initialize();
            _maxEntries = Parameters[2].AsInt;
            _considerIntraday = Parameters[3].AsBoolean;
        }

        //Size the position, if max entries exceeded, set size to zero
        public override double SizePosition(Transaction t, BarHistory bars, int idx, double basisPrice, double equity, double cash)
        {
            int count = 0;

            if (!_considerIntraday)
            {
                foreach (Transaction o in Orders)
                    if (o.TransactionType.IsEntry())
                        count++;
            }
            else
            {
                if (t.Bars.Scale.IsIntraday)
                {
                    var positionsInSymbolOpenedToday = Positions.AsParallel().AsOrdered().Reverse().TakeWhile(p => p.EntryDate.DayOfYear == t.EntryDate.DayOfYear && p.Symbol == t.Symbol);
                    count += positionsInSymbolOpenedToday.Count();
                }
            }
            
            if (count >= _maxEntries)
                return 0.0;
            else
                return base.SizePosition(t, bars, idx, basisPrice, equity, cash);
        }

        //private members
        private int _maxEntries;
        private bool _considerIntraday;
    }
}