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

Position Sizer API

This document details the API for building Position Sizer extensions for Wealth-Lab 7. 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 WL7 position sizing user interface section, in the Strategy Settings tab of the Strategy window.

You can create a Position Sizer in a .NET development tool such as Visual Studio, or Visual Studio Code. Create a class library project that targets .NET Core 3.1, then add the following references to your project:

  • WealthLab.Core
  • WealthLab.Backtest

Your Position Sizer will be a class in this library that descends from WealthLab.Backtest's PositionSizerBase. After you implement and build your library, simply copy the resulting assembly DLL into the WL7 program folder. The next time WL7 starts up, it should find your Position Sizer, and it will be available in a drop down list when Position Size: Advanced Pos Sizer is selected in the Strategy Settings tab of the Strategy window.

Create Class Library in Visual Studio

IHost Interface

The IHost interface allows your extension to access information about the user's WL7 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 WL7.

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 WL7'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.

Configuration

Position Sizers will often require configuration, such as exposing one or more parameters that the user can adjust. The PositionSizerBase class derives from a class called Configurable which provides support for configuration parameters. You can utilize the following properties and methods provided by Configurable, or implement a configuration solution of your own if required.

public override bool IsConfigurable

Return true here to indicate that your Position Sizer requires configuration before use.

public ParameterList Parameters

A ParameterList object that represents the parameters of your Position Sizer. You can utilize this mechanism by adding Parameter instances to this property.

 public string Configuration

This contains the string that represents the configuration that was established by the user. The default implementation uses a parameter editor dialog that the user interacts with the edit the parameter values, and the Configuration string represents the persisted values of the Parameters.

public virtual string EditConfig()

WL7 calls this method when the user configures your Position Sizer. The default implementation brings up a parameter editor dialog that the user interacts with to edit the Parameters instances. You can optionally override this to show a different modal dialog window. If you do so, you should return a string that represents the Configuration established by the user.

public virtual string ConfigKey

WL7 stores your Position Sizer's configuration in the settings file, keyed with a string based on this property. The default value is the Position Sizer's Name + "_Configuration".

public virtual void ProcessConfig()

This is called after the user changes the configuration. You can override this if you need to perform any special processing at this point, but be sure to call base.ProcessConfig().

Supporting Parameters - Single Component

If you're building a standalone Provider that supports Configuration, add Parameters in the constructor, and then call LoadConfig to load in any previously saved Parameter values. Finally, assign the persisted Parameters to the Configuration property. Below is an example based off our CSI Extension:

//constructor        
public CSIProvider() : base()
{
   Parameters.Add(new Parameter("Apply Stock Split Adjustments", ParameterTypes.Boolean, true));
   Parameters.Add(new Parameter("Apply Dividend Adjustments", ParameterTypes.Boolean, false));
   Parameters.Add(new Parameter("Proportional Dividend Adjustment", ParameterTypes.Boolean, false));
   LoadConfig();
   Configuration = Parameters.Persist();
}

Supporting Parameters - Multiple Components

If your Extension offers a Historical Data Provider, a Streaming Data Provider, and a Broker Provider, you might want to share Configuration amongst these components. Follow this pattern to achieve this, using our Alpaca Extension here as an example.

First, we created a separate "Alpaca configuration" class that would manage the master version of the Parameters. This is the constructor.

        //constructor
        public AlpacaConfig()
        {
            Parameters.Add(new Parameter("API Key", ParameterTypes.String, ""));
            Parameters.Add(new Parameter("Secret Key", ParameterTypes.Password, ""));
            Parameters.Add(new Parameter("Paper Trading Account?", ParameterTypes.Boolean, false));
            LoadConfig();
        }

We added a method that the Historical, Streaming, and Broker providers could call whenever their parameters changed. It's called in their respective ProcessConfig method overrides:

        //broker, historical, streaming, call this when their parameters change
        public void ParametersChanged(ParameterList pl)
        {
            Parameters = pl.Clone();
            Configuration = Parameters.Persist();
            SaveConfig();
        }

Now, in the respective provider, we override the ConfigKey to ensure that they all share the same persisted parameters:

        //Share config key
        public override string ConfigKey => "Alpaca_Configuration";

Their respective constructors pull in the parameters from the master configuration object:

        //constructor
        public AlpacaHistorical() : base()
        {
            Parameters = AlpacaConfig.Instance.Parameters;
            Configuration = Parameters.Persist();
        }

And finally in their ProcessConfig methods, they pass on the parameter changes to the master configuration object.

        //save config change in AlpacaConfig parameters
        public override void ProcessConfig()
        {
            base.ProcessConfig();
            AlpacaConfig.Instance.ParametersChanged(Parameters);
            Parameters = AlpacaConfig.Instance.Parameters;
        }
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.

Sizing Positions

public virtual void Initialize()

WL7 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)

WL7 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.

Simulation Related Properties

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.

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 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)

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
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 WL7. 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.Collections.Generic;
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
    {
        //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;

        //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));
        }
    }
}