Position Object

Represents a single Position (trade) that was created by the Strategy.  Use the Positions property to access all of the trades that have been created so far at any point in time.

BuyAtMarket

Active Property

bool Active

Determines if the Position is still open or not.  A position is closed when it is successfully sold (long positions) or covered (short positions).


Example

protected override void Execute(){
    DataSeries rsi = RSI.Series( ( (High+Low)/2 ), 40 );
    
    Position i;
    
    for(int bar = rsi.FirstValidValue; bar < Bars.Count; bar++)
    {
        if ( CrossOver( bar, rsi, 35 ) )
            BuyAtLimit( bar+1, High[bar] );

        if ( CrossUnder ( bar, rsi, 70 ) )
        {
            // Cycle through open positions
            foreach( Position p in Positions )
                if ( p.Active )
                    SellAtMarket( bar+1, p );
        }
    }
}
BuyAtMarket

AutoProfitLevel Property

double AutoProfitLevel

Specifies the initial profit target level (price) of the Position. The value, analogous to RiskStopLevel, is the price at which the same-bar Limit order should be placed. It is valid for any BarScale.

Remarks


Example

protected override void Execute(){
PlotStops();
    int bcm1 = Bars.Count - 1;
    DataSeries sma1 = SMA.Series(Close, 8);
    DataSeries sma2 = SMA.Series(Close, 20);
    PlotSeries(PricePane, sma1, Color.Green, LineStyle.Solid, 1);
    PlotSeries(PricePane, sma2, Color.Red, LineStyle.Solid, 1);

    for(int bar = Bars.FirstActualBar + 20; bar < Bars.Count; bar++)
    {
        if (IsLastPositionActive)
        {
            Position p = LastPosition;
            SellAtLimit( bar+1, p, p.AutoProfitLevel * 1.01 );
        }
        else if ( CrossOver(bar, sma1, sma2) )
        {
            AutoProfitLevel = Bars.High[bar];
            // also use same-bar exit for backtesting
            if (BuyAtMarket( bar+1 ) != null && bar < bcm1) 
                SellAtLimit( bar + 1, LastPosition, LastPosition.AutoProfitLevel, "same-bar exit" ); 
        }
    }
}
BuyAtMarket

Bars Property

Bars Bars

Returns the Bars object that the Position was traded against.  Certain Strategies (such as pairs trading or symbol rotation) can trade on multiple symbols.  The Bars property allows you to determine which symbol a particular Position was established on.

Remarks

BuyAtMarket

BarsHeld Property

int BarsHeld

Returns the number of bars that the Position was held.  If the Position is still active, BarsHeld returns the total number of bars held as of the last bar of the chart.  The BarsHeld property is primarily intended for use by Performance Visualizers, not Strategies.


Example

protected override void Execute(){
    // Return the total number of bars held as of the last bar of the chart
    
    for(int bar = 20; bar < Bars.Count; bar++)
    {
        if (IsLastPositionActive)
        {
            if( bar == LastPosition.Bars.Count-1 )
            DrawLabel( PricePane, "Bars held: " + LastPosition.BarsHeld , Color.Blue );
        }
        else
        {
            BuyAtMarket( bar+1 );
        }
    }
}
BuyAtMarket

BasisPrice Property

double BasisPrice

Returns the Position's "basis price".  This is the price that was used to establish how many shares the Position should be sized to.  For market orders, the basis price is typically the closing price of the previous bar.  The actual entry price can of course differ because the market may open above or below the previous close.  In certain situation (unless a margin factor is applied to simulations), this difference can cause a trade to not be executed (even a market order) due to insufficient capital.  For limit orders, the basis price is always the limit price of the order.  For stop orders, the basis price is always the stop price specified.


Example

protected override void Execute(){
    // Display differences between Basis Price and Entry Price
    
    for(int bar = 4; bar < Bars.Count; bar++)
    {
        if (IsLastPositionActive)
        {
            if( bar == LastPosition.Bars.Count-1 )
                DrawLabel( PricePane, (LastPosition.EntryPrice - LastPosition.BasisPrice).ToString(), Color.Black ) ;
            SellAtStop( bar+1, LastPosition, Lowest.Series( Low, 3 )[bar] );
        }
        else
        {
            BuyAtStop( bar+1, Highest.Series( High, 3 )[bar] );
        }
    }
}
BuyAtMarket

EntryBar Property

int EntryBar

Returns the bar number that the Position was entered on.

Remarks

  • (Doesn't affect WealthScript Strategy coding). In development of PosSizers and Performance Visualizers, checking for EntryBar or ExitBar in portfolio simulations may produce unexpected results because the different historical DataSets aren't synchronized when backtesting.

  • Solution: check for the date with EntryDate/ExitDate rather than the bar number:

    // Fails:
    //if (Positions[n].EntryBar == bar + 1)
    // Workaround:
    if(Positions[n].EntryDate == bars.Date[bar + 1].Date)


Example

protected override void Execute(){
    for(int bar = 20; bar < Bars.Count; bar++)
    {
        if (!IsLastPositionActive)
        {
            if ( StochK.Series( Bars, 20 )[bar] > 70 )
            BuyAtMarket( bar+1, "Stoch" );
        }
        else
        {
            // Sell on 10th day
            if ( bar+1 - LastPosition.EntryBar == 10 )
            SellAtClose( bar, LastPosition, "10 day" );
        }
    }
}
BuyAtMarket

EntryCommission Property

double EntryCommission

Returns the commission value that was applied to the entry trade for the Position.

Remarks

  • EntryCommission is not available during Strategy execution, and is only available to Performance Visualizers, Commission structures and PosSizers that execute after position sizing has been applied.
BuyAtMarket

EntryDate Property

DateTime EntryDate

Returns the date/time that the Position was entered on.


Example

protected override void Execute(){
    // Dumps entry dates to Debug window
    // Run this on Daily
    if ( Bars.IsIntraday != true )
    {
        for(int bar = 50; bar < Bars.Count; bar++)
        {
            if (IsLastPositionActive)
            {
                PrintDebug( LastPosition.EntryDate );
                // Sell after 10 days
                if ( bar+1 == Bars.ConvertDateToBar( LastPosition.EntryDate, false ) + 10  )
                    SellAtMarket( bar+1, LastPosition, "10 day" );
            }
            else
            {
                if ( StochK.Series( Bars, 20 )[bar] > 70 )
                    BuyAtMarket( bar+1, "Stoch" );
            }
        }
    } else
        Abort();
}
BuyAtMarket

EntryOrderType Property

OrderType EntryOrderType

Returns the type of order that was used to establish the Position.  Possible values are:

  • OrderType.Market
  • OrderType.Limit
  • OrderType.Stop
  • OrderType.AtClose

Example

protected override void Execute(){
    for(int bar = 20; bar < Bars.Count; bar++)
    {
        double atr = ATR.Series( Bars,10 )[bar];
        
        if (IsLastPositionActive)
        {
            string signal = LastPosition.EntrySignal;
            
            // Simple switching of exits depending on entry order type
            if ( LastPosition.EntryOrderType == OrderType.Limit )
                SellAtStop( bar+1, LastPosition, Lowest.Series( Low,40 )[bar], "Breakdown" );
            else
                if( LastPosition.EntryOrderType == OrderType.Stop )
                SellAtLimit( bar+1, LastPosition, Close[bar]+2*atr, "Target" );
        }
        else
        {
            if ( BuyAtLimit( bar+1, Lowest.Series( Low,40 )[bar], "Deep down" ) == null )
                BuyAtStop( bar+1, Bars.Close[bar]+atr, "Range" );
        }
    }
}
BuyAtMarket

EntryPrice Property

double EntryPrice

Returns the entry price of the Position.


Example

protected override void Execute(){
    // Use an ATR stop based on the entry price
    
    DataSeries sma1 = SMA.Series( Close, 10 );
    DataSeries sma2 = SMA.Series( Close, 40 );
    PlotSeries( PricePane, sma1, Color.LightCoral, WealthLab.LineStyle.Solid, 1 );
    PlotSeries( PricePane, sma2, Color.DarkGreen, WealthLab.LineStyle.Solid, 1 );

    for(int bar = sma2.FirstValidValue; bar < Bars.Count; bar++)
    {
        if (IsLastPositionActive)
        {
            if ( CrossUnder( bar, sma1, sma2 ) )
                SellAtMarket( bar+1, LastPosition, "SMA" );
            else 
            if ( Close[bar] < ( LastPosition.EntryPrice - ATR.Series( Bars, 10 )[bar] * 4 ) )
                SellAtMarket( bar+1, LastPosition, "ATR" );
        }
        else
        {
            if ( CrossOver( bar, sma1, sma2 ) )
                BuyAtMarket( bar+1, "SMA" );
        }
    }
}
BuyAtMarket

EntrySignal Property

string EntrySignal

Returns the "signal name" that was supplied in the "BuyAtXXX" or "ShortAtXXX" method that was used to establish the Position.  All "BuyAtXXX" and "ShortAtXXX" methods allow you to specify an optional signalName parameter.  The value that you specify there is visible in the trade list, and is also accessible via the EntrySignal property.


Example

protected override void Execute(){
    for(int bar = 40; bar < Bars.Count; bar++)
    {
        double atr = ATR.Series( Bars,10 )[bar];
        
        if (IsLastPositionActive)
        {
            string signal = LastPosition.EntrySignal;
            
            // Simple switching of exits depending on entry signal
            if ( signal == "Highest" )
                SellAtStop( bar+1, LastPosition, Lowest.Series( Low,40 )[bar], "Breakdown" );
            else
                if( signal == "Range" )
                SellAtLimit( bar+1, LastPosition, Close[bar]+2*atr, "Target" );
        }
            else
            {
                if ( BuyAtStop( bar+1, Highest.Series( High,40 )[bar], "Highest" ) == null )
                    BuyAtStop( bar+1, Bars.Close[bar]+atr, "Range" );
            }
    }
}
BuyAtMarket

ExitBar Property

int ExitBar

Returns the bar number that the Position was exited (closed) on.  If the Position is still active, ExitBar returns -1.

Remarks

  • (Doesn't affect WealthScript Strategy coding). In development of PosSizers and Performance Visualizers, checking for EntryBar or ExitBar in portfolio simulations may produce unexpected results because the different historical DataSets aren't synchronized when backtesting.

  • Solution: check for the date with EntryDate/ExitDate rather than the bar number:

    // Fails:
    //if (Positions[n].ExitBar == bar + 1)
    // Workaround:
    if(Positions[n].ExitDate == bars.Date[bar + 1].Date)


Example

using System;
using System.Text;
using System.Drawing;
using WealthLab;

namespace WealthLab.Strategies
{
    public class ExitBar : WealthScript
    {
        // Display the shortest and the longest holding time of closed positions

        protected override void Execute()
        {
            for(int bar = 30; bar < Bars.Count; bar++)
            {
                if (!IsLastPositionActive )
                {
                    if ( CrossUnder( bar, Indicators.RSI.Series( Close, 10 ), 20 ) )
                        BuyAtMarket( bar+1 );
                }
                else
                {
                    if ( CrossOver( bar, Indicators.RSI.Series( Close, 10 ), 60 ) )
                        SellAtMarket( bar+1, LastPosition );
                }
            }
            
            int LowBar = 0;
            int HighBar = 0;
            int BarsHeld;
            
            foreach ( Position p in Positions )
            {
                if( !p.Active )
                {
                    BarsHeld = p.ExitBar - p.EntryBar;
                    if( BarsHeld > HighBar )
                        HighBar = BarsHeld;
                    if ( ( BarsHeld < LowBar ) | ( LowBar <= 0 ) )
                        LowBar = BarsHeld;
                }
            }
            
            DrawLabel( PricePane, "Longest Holding Time: " + HighBar, Color.Black );
            DrawLabel( PricePane, "Shortest Holding Time: " + LowBar, Color.Black );
        }
    }
}
BuyAtMarket

ExitCommission Property

double ExitCommission

Returns the commission value that was applied to the exit trade for the Position.  If the Position is still active, ExitCommission returns 0.

Remarks

  • ExitCommission is not available during Strategy execution, and is only available to Performance Visualizers, Commission structures and PosSizers that execute after position sizing has been applied.
BuyAtMarket

ExitDate Property

DateTime ExitDate

Returns the date/time that the Position was exited (closed) on.  If the Position is still active, ExitDate returns DateTime.MinValue.


Example

protected override void Execute(){
    for (int bar = 41; bar < Bars.Count; bar++)
    {
        if( IsLastPositionActive )
        {
            if( bar == Bars.Count-1 ) 
                DrawLabel( PricePane, "Holding a position...", Color.LightBlue );
            SellAtStop( bar+1, LastPosition, Lowest.Series( Low, 20 )[bar], "Breakdown" );
        }
        else
        {
            // Prints how much time passed since last exit from LastPosition
            if( ( bar == Bars.Count-1 ) & ( Positions.Count > 0 ) )
            {
                int x = DateTime.Compare(Bars.Date[bar], LastPosition.ExitDate);
                if( x > 0 )
                {
                    DateTime exitDate = LastPosition.ExitDate;
                    DateTime today = Bars.Date[bar];
                    TimeSpan sinceExit = today.Subtract( exitDate );
                    DrawLabel( PricePane, "Time since last exit: " + sinceExit.Days + " days, " + sinceExit.Hours + " hours, " + sinceExit.Minutes + " minutes, " + sinceExit.Seconds + " seconds", Color.Blue );
                }    
            }
            BuyAtStop( bar+1, Highest.Series( High, 40 )[bar], "Breakout" );
        }
    }
}
BuyAtMarket

ExitOrderType Property

OrderType ExitOrderType

Returns the type of order that was used to exit (close) the Position.  Possible values are:

  • OrderType.Market
  • OrderType.Limit
  • OrderType.Stop
  • OrderType.AtClose

Example

protected override void Execute(){
    ClearDebug();
    
    for(int bar = 20; bar < Bars.Count; bar++)
    {
        double atr = ATR.Series( Bars,10 )[bar];
        if (IsLastPositionActive)
        {
            if(    SellAtStop( bar+1, LastPosition, Lowest.Series( Low,40 )[bar] ) == false )
                SellAtLimit( bar+1, LastPosition, Close[bar]+2*atr );
        }
        else
        {
            BuyAtStop( bar+1, Highest.Series( High,20 )[bar] );
        }
    }

    // Print exit order type statistics
    
    int stop = 0;
    int limit = 0;

    foreach( Position p in Positions )
        if( !p.Active )
        {
            if( p.ExitOrderType == OrderType.Stop )
                stop++;
            if( p.ExitOrderType == OrderType.Limit )
                limit++;
        }
    PrintDebug( "Exits on stop: " + stop, "Exits at limits: " + limit );
}
BuyAtMarket

ExitPrice Property

double ExitPrice

Returns the exit price of the Position.  If the Position is still active, ExitPrice returns 0.


Example

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using WealthLab;
using WealthLab.Indicators;

namespace WealthLab.Strategies
{
    public class MyStrategy : WealthScript
    {
        // This procedure reports the entry and exit levels of all trades
        void TradeReport()
        {
            foreach( Position p in Positions )
            {
                PrintDebug( "Entry:" + p.EntryDate + " at " + p.EntryPrice );
                PrintDebug( "Exit:" + p.ExitDate + " at " + p.ExitPrice );
            }
        }

        protected override void Execute()
        {
            for(int bar = 20; bar < Bars.Count; bar++)
            {
                if (IsLastPositionActive)
                    SellAtStop( bar+1, LastPosition, Lowest.Series(Close,20)[bar], "Exit" );
                else
                    BuyAtStop( bar+1, Close[bar]+2*ATR.Series(Bars,10)[bar], "Long" );
            }
            TradeReport();
        }
    }
}
BuyAtMarket

ExitSignal Property

string ExitSignal

Returns the "signal name" that was supplied in the "SellAtXXX" or "CoverAtXXX" method that was used to close the Position.  All "SellAtXXX" and "CoverAtXXX" methods allow you to specify an optional signalName parameter.  The value that you specify there is visible in the trade list, and is also accessible via the ExitSignal property.  If the Position is still active, ExitSignal returns a blank string.


Example

protected override void Execute(){
    // Marks the position exit bar with the exit signal name
    
    for(int bar = 20; bar < Bars.Count; bar++)
    {
        double atr = ATR.Series( Bars,10 )[bar];
        
        if (IsLastPositionActive)
        {
            Position p = LastActivePosition;
            if ( SellAtStop( bar+1, LastPosition, Lowest.Series( Low,10 )[bar], "Breakdown" ) )
                AnnotateBar( p.ExitSignal.ToString(), bar, false, Color.Red ); else                        
            if( SellAtLimit( bar+1, LastPosition, Close[bar]+atr, "Target" ) )
                AnnotateBar( p.ExitSignal.ToString(), bar, true, Color.Blue );
            
        }
        else
            BuyAtStop( bar+1, Highest.Series( High, 20 )[bar] );
    }
}
BuyAtMarket

HighestHighAsOfBar

double HighestHighAsOfBar(int bar);

Returns the highest price registered in the Position, as of the specified bar number. 


Example

protected override void Execute(){
    for(int bar = 50; bar < Bars.Count; bar++)
    {
        if (IsLastPositionActive)
        {    
            Position p = LastPosition;
            SellAtTrailingStop( bar+1, p, p.HighestHighAsOfBar(bar) - 3*ATR.Value( bar, Bars, 14 ), "3x ATR Stop" );
        }
        else
            BuyAtStop( bar+1, Highest.Series( High, 20 )[bar] );
    }
}
BuyAtMarket

LowestLowAsOfBar

double LowestLowAsOfBar(int bar);

Returns the lowest price registered in the Position, as of the specified bar number. 


Example

protected override void Execute(){
    for(int bar = 50; bar < Bars.Count; bar++)
    {
        if (IsLastPositionActive)
        {    
            Position p = LastPosition;
            CoverAtTrailingStop( bar+1, p, p.LowestLowAsOfBar(bar) + 3*ATR.Value( bar, Bars, 14 ), "3x ATR Stop" );
        }
        else
            ShortAtStop( bar+1, Lowest.Series( Low, 20 )[bar] );
    }
}
BuyAtMarket

MAE Property

double MAE

Returns the Maximum Adverse Excursion (MAE) that was generated by the Position, with commissions applied.  MAE represents the largest intraday loss that the trade experienced during its lifetime.  This property is intended for use by Performance Visualizers, and not in Strategies.

Remarks

  • MAE is not available during Strategy execution, and is only available to Performance Visualizers that execute after position sizing has been applied.
BuyAtMarket

MAEAsOfBar

double MAEAsOfBar(int bar);

Returns the Maximum Adverse Excursion (MAE) that was generated by the Position, with commissions applied, as of the specified bar number.  MAEAsOfBar represents the largest intraday loss that the trade experienced up to the specified bar.

Remarks

  • In portfolio simulation mode, all trades are pre-executed using 1 share per Position, and then position sizing is applied after the fact.  So the MAEAsOfBar property will always be based on 1 share while the Strategy is executing.
  • The MAEAsOfBar property is always available to Performance Visualizers, which execute after the position sizing has been applied.
  • Problem: Having split a Position into two with SplitPosition, the following properties incorrectly report 0 or NaN for the first part of the splitted Position if Strategy is run in a Portfolio Simulation mode: MFEAsOfBarPercent, MFEAsOfBar, MAEAsOfBarPercent, MAEAsOfBar, NetProfitAsOfBarPercent, NetProfitAsOfBar.
    • Partial workaround: Switch to a Raw Profit position sizing mode.

Example

protected override void Execute(){
    // Record a position's Maximum Adverse Excursion (MAE) at each bar as a Data Series.
    // This system buys on a crossover of a 30-period weighted
    // moving average and sells after 20 bars.

    int timedExit = 20; // just exit after 20 days
    DataSeries hMA = WMA.Series( Close, 30 );
    DataSeries hMAESer = new DataSeries( Bars, "MAE" );

    for(int bar = hMA.FirstValidValue; bar < Bars.Count; bar++)
    {
        if (IsLastPositionActive)
        {
            hMAESer[bar] = LastActivePosition.MAEAsOfBar( bar );
            if ( bar+1 - LastPosition.EntryBar >= timedExit )
                SellAtMarket( bar+1, LastPosition );
        }
        else
        {
            if( CrossOver( bar, Close, hMA[bar-1] ) )
                BuyAtMarket( bar+1, "Xover" );

        }
    }
    
    ChartPane ProfitPane = CreatePane( 50, true, true );
    PlotSeries( ProfitPane, hMAESer, Color.DarkGreen, WealthLab.LineStyle.Histogram, 1 );
    PlotSeries( PricePane, hMA, Color.Blue, WealthLab.LineStyle.Solid, 2 );
}
BuyAtMarket

MAEAsOfBarPercent

double MAEAsOfBarPercent(int bar);

Returns the Maximum Adverse Excursion (MAE) that was generated by the Position, with commissions applied, as a percentage, as of the specified bar number.  MAEAsOfBarPercent represents the largest intraday percentage loss that the trade experienced up to the specified bar.

Remarks

  • In portfolio simulation mode, all trades are pre-executed using 1 share per Position, and then position sizing is applied after the fact.  So the MAEAsOfBarPercent property will always be based on 1 share while the Strategy is executing.
  • The MAEAsOfBarPercent property is always available to Performance Visualizers, which execute after the position sizing has been applied.
  • Problem: Having split a Position into two with SplitPosition, the following properties incorrectly report 0 or NaN for the first part of the splitted Position if Strategy is run in a Portfolio Simulation mode: MFEAsOfBarPercent, MFEAsOfBar, MAEAsOfBarPercent, MAEAsOfBar, NetProfitAsOfBarPercent, NetProfitAsOfBar.
    • Partial workaround: Switch to a Raw Profit position sizing mode.

Example

protected override void Execute(){
    // Record a position's Maximum Adverse Excursion (MAE) percentage
    // at each bar as a Data Series.
    // This system buys on a crossover of a 30-period weighted
    // moving average and sells after 20 bars.

    int timedExit = 20; // just exit after 20 days
    DataSeries hMA = WMA.Series( Close, 30 );
    DataSeries hMAEPctSer = new DataSeries( Bars, "MAE (Percentage)" );

    for(int bar = hMA.FirstValidValue; bar < Bars.Count; bar++)
    {
        if (IsLastPositionActive)
        {
            hMAEPctSer[bar] = LastActivePosition.MAEAsOfBarPercent( bar );
            if ( bar+1 - LastPosition.EntryBar >= timedExit )
                SellAtMarket( bar+1, LastPosition );
        }
        else
        {
            if( CrossOver( bar, Close, hMA[bar-1] ) )
                BuyAtMarket( bar+1, "Xover" );

        }
    }
    
    ChartPane ProfitPane = CreatePane( 50, true, true );
    PlotSeries( ProfitPane, hMAEPctSer, Color.DarkGreen, WealthLab.LineStyle.Histogram, 1 );
    PlotSeries( PricePane, hMA, Color.Blue, WealthLab.LineStyle.Solid, 2 );
}
BuyAtMarket

MAEPercent Property

double MAEPercent

Returns the Maximum Adverse Excursion (MAE) that was generated by the Position, with commissions applied, as a percentage.  MAEPercent represents the largest intraday percentage loss that the trade experienced during its lifetime.  This property is intended for use by Performance Visualizers, and not in Strategies.

Remarks

  • MAEPercent is not available during Strategy execution, and is only available to Performance Visualizers that execute after position sizing has been applied.
BuyAtMarket

MFE Property

double MFE

Returns the Maximum Favorable Excursion (MFE) that was generated by the Position, with commissions applied.  MFE represents the highest intraday profit that the trade experienced during its lifetime.  This property is intended for use by Performance Visualizers, and not in Strategies.

Remarks

  • MFE is not available during Strategy execution, and is only available to Performance Visualizers that execute after position sizing has been applied.
BuyAtMarket

MFEAsOfBar

double MFEAsOfBar(int bar);

Returns the Maximum Favorable Excursion (MFE) that was generated by the Position, with commissions applied, as of the specified bar number.  MFEAsOfBar represents the highest intraday profit that the trade experienced up to the specified bar.

Remarks

  • In portfolio simulation mode, all trades are pre-executed using 1 share per Position, and then position sizing is applied after the fact.  So the MFEAsOfBar property will always be based on 1 share while the Strategy is executing.
  • The MFEAsOfBar property is always available to Performance Visualizers, which execute after the position sizing has been applied.
  • Problem: Having split a Position into two with SplitPosition, the following properties incorrectly report 0 or NaN for the first part of the splitted Position if Strategy is run in a Portfolio Simulation mode: MFEAsOfBarPercent, MFEAsOfBar, MAEAsOfBarPercent, MAEAsOfBar, NetProfitAsOfBarPercent, NetProfitAsOfBar.
    • Partial workaround: Switch to a Raw Profit position sizing mode.

Example

protected override void Execute(){
    // Record a position's Maximum Favorite Excursion (MFE) at each bar as a Data Series.
    // This system buys on a crossover of a 30-period weighted
    // moving average and sells after 20 bars.

    int timedExit = 20; // just exit after 20 days
    DataSeries hMA = WMA.Series( Close, 30 );
    DataSeries hMFESer = new DataSeries( Bars, "MFE" );

    for(int bar = hMA.FirstValidValue; bar < Bars.Count; bar++)
    {
        if (IsLastPositionActive)
        {
            hMFESer[bar] = LastActivePosition.MFEAsOfBar( bar );
            if ( bar+1 - LastPosition.EntryBar >= timedExit )
                SellAtMarket( bar+1, LastPosition );
        }
        else
        {
            if( CrossOver( bar, Close, hMA[bar-1] ) )
                BuyAtMarket( bar+1, "Xover" );

        }
    }
    
    ChartPane ProfitPane = CreatePane( 50, true, true );
    PlotSeries( ProfitPane, hMFESer, Color.DarkGreen, WealthLab.LineStyle.Histogram, 1 );
    PlotSeries( PricePane, hMA, Color.Blue, WealthLab.LineStyle.Solid, 2 );
}
BuyAtMarket

MFEAsOfBarPercent

double MFEAsOfBarPercent(int bar);

Returns the Maximum Favorable Excursion (MFE) that was generated by the Position, with commissions applied, as a percentage, as of the specified bar number.  MFEAsOfBarPercent represents the highest intraday percentage profit that the trade experienced up to the specified bar.

Remarks

  • In portfolio simulation mode, all trades are pre-executed using 1 share per Position, and then position sizing is applied after the fact.  So the MFEAsOfBarPercent property will always be based on 1 share while the Strategy is executing.
  • The MFEAsOfBarPercent property is always available to Performance Visualizers, which execute after the position sizing has been applied.
  • Problem: Having split a Position into two with SplitPosition, the following properties incorrectly report 0 or NaN for the first part of the splitted Position if Strategy is run in a Portfolio Simulation mode: MFEAsOfBarPercent, MFEAsOfBar, MAEAsOfBarPercent, MAEAsOfBar, NetProfitAsOfBarPercent, NetProfitAsOfBar.
    • Partial workaround: Switch to a Raw Profit position sizing mode.

Example

protected override void Execute(){
    // Record a position's Maximum Favorable Excursion (MFE) percentage
    // at each bar as a Data Series.
    // This system buys on a crossover of a 30-period weighted
    // moving average and sells after 20 bars.

    int timedExit = 20; // just exit after 20 days
    DataSeries hMA = WMA.Series( Close, 30 );
    DataSeries hMFEPctSer = new DataSeries( Bars, "MFE (Percentage)" );

    for(int bar = hMA.FirstValidValue; bar < Bars.Count; bar++)
    {
        if (IsLastPositionActive)
        {
            hMFEPctSer[bar] = LastActivePosition.MFEAsOfBarPercent( bar );
            if ( bar+1 - LastPosition.EntryBar >= timedExit )
                SellAtMarket( bar+1, LastPosition );
        }
        else
        {
            if( CrossOver( bar, Close, hMA[bar-1] ) )
                BuyAtMarket( bar+1, "Xover" );

        }
    }
    
    ChartPane ProfitPane = CreatePane( 50, true, true );
    PlotSeries( ProfitPane, hMFEPctSer, Color.DarkGreen, WealthLab.LineStyle.Histogram, 1 );
    PlotSeries( PricePane, hMA, Color.Blue, WealthLab.LineStyle.Solid, 2 );
}
BuyAtMarket

MFEPercent Property

double MFEPercent

Returns the Maximum Favorable Excursion (MFE) that was generated by the Position, with commissions applied, as a percentage.  MFEPercent represents the highest intraday percentage profit that the trade experienced during its lifetime.  This property is intended for use by Performance Visualizers, and not in Strategies.

Remarks

  • MFEPercent is not available during Strategy execution, and is only available to Performance Visualizers that execute after position sizing has been applied.
BuyAtMarket

NetProfit Property

double NetProfit

Returns the profit that was generated by the Position, excluding commissions.  This property is intended for use by Performance Visualizers, and not in Strategies.

Remarks

  • In portfolio simulation mode, all trades are pre-executed using 1 share per Position, and then position sizing is applied after the fact.  So the NetProfit property will always be based on 1 share while the Strategy is executing.
  • The NetProfit property is always available to Performance Visualizers, which execute after the position sizing has been applied.
BuyAtMarket

NetProfitAsOfBar

double NetProfitAsOfBar(int bar);

Returns the profit that was generated by the Position, excluding commissions, as of the specified bar number.

Remarks

  • In portfolio simulation mode, all trades are pre-executed using 1 share per Position, and then position sizing is applied after the fact.  So NetProfitAsOfBar will always be based on 1 share while the Strategy is executing.
  • NetProfitAsOfBar is always available to Performance Visualizers, which execute after the position sizing has been applied.
  • Problem: Having split a Position into two with SplitPosition, the following properties incorrectly report 0 or NaN for the first part of the splitted Position if Strategy is run in a Portfolio Simulation mode: MFEAsOfBarPercent, MFEAsOfBar, MAEAsOfBarPercent, MAEAsOfBar, NetProfitAsOfBarPercent, NetProfitAsOfBar.
    • Partial workaround: Switch to a Raw Profit position sizing mode.

Example

protected override void Execute(){
    // Record a position's net profit at each bar as a Data Series.
    // This system buys on a crossover of a 30-period weighted
    // moving average and sells after 20 bars.

    int timedExit = 20; // just exit after 20 days
    DataSeries hMA = WMA.Series( Close, 30 );
    DataSeries hPftSer = new DataSeries( Bars, "Net Profit" );

    for(int bar = hMA.FirstValidValue; bar < Bars.Count; bar++)
    {
        if (IsLastPositionActive)
        {
            hPftSer[bar] = LastActivePosition.NetProfitAsOfBar( bar );
            if ( bar+1 - LastPosition.EntryBar >= timedExit )
                SellAtMarket( bar+1, LastPosition );
        }
        else
        {
            if( CrossOver( bar, Close, hMA[bar-1] ) )
                BuyAtMarket( bar+1, "Xover" );

        }
    }
    
    ChartPane ProfitPane = CreatePane( 50, true, true );
    PlotSeries( ProfitPane, hPftSer, Color.DarkGreen, WealthLab.LineStyle.Histogram, 1 ); //'Open Profit' 
    PlotSeries( PricePane, hMA, Color.Blue, WealthLab.LineStyle.Solid, 2 );
}
BuyAtMarket

NetProfitAsOfBarPercent

double NetProfitAsOfBarPercent(int bar);

Returns the percentage profit that was generated by the Position, excluding commissions, as of the specified bar number.

Remarks

  • In portfolio simulation mode, all trades are pre-executed using 1 share per Position, and then position sizing is applied after the fact.  So NetProfitAsOfBarPercent will always be based on 1 share while the Strategy is executing.
  • NetProfitAsOfBarPercent is always available to Performance Visualizers, which execute after the position sizing has been applied.
  • Problem: Having split a Position into two with SplitPosition, the following properties incorrectly report 0 or NaN for the first part of the splitted Position if Strategy is run in a Portfolio Simulation mode: MFEAsOfBarPercent, MFEAsOfBar, MAEAsOfBarPercent, MAEAsOfBar, NetProfitAsOfBarPercent, NetProfitAsOfBar.
    • Partial workaround: Switch to a Raw Profit position sizing mode.

Example

protected override void Execute(){
    // Record a position's percentage profit at each bar as a Data Series.
    // This system buys on a crossover of a 30-period weighted
    // moving average and sells after 20 bars.

    int timedExit = 20; // just exit after 20 days
    DataSeries hMA = WMA.Series( Close, 30 );
    DataSeries hPftPctSer = new DataSeries( Bars, "Net Profit (Percent)" );

    for(int bar = hMA.FirstValidValue; bar < Bars.Count; bar++)
    {
        if (IsLastPositionActive)
        {
            hPftPctSer[bar] = LastActivePosition.NetProfitAsOfBarPercent( bar );
            if ( bar+1 - LastPosition.EntryBar >= timedExit )
                SellAtMarket( bar+1, LastPosition );
        }
        else
        {
            if( CrossOver( bar, Close, hMA[bar-1] ) )
                BuyAtMarket( bar+1, "Xover" );

        }
    }
    
    ChartPane ProfitPane = CreatePane( 50, true, true );
    PlotSeries( ProfitPane, hPftPctSer, Color.DarkGreen, WealthLab.LineStyle.Histogram, 1 ); //'Open Profit' 
    PlotSeries( PricePane, hMA, Color.Blue, WealthLab.LineStyle.Solid, 2 );
}
BuyAtMarket

NetProfitPercent Property

double NetProfitPercent

Returns the profit that was generated by the Position, excluding commissions, as a percentage.  This property is intended for use by Performance Visualizers, and not in Strategies.

Remarks

  • In portfolio simulation mode, all trades are pre-executed using 1 share per Position, and then position sizing is applied after the fact.  So the NetProfitPercent property will always be based on 1 share while the Strategy is executing.
  • The NetProfitPercent property is always available to Performance Visualizers, which execute after the position sizing has been applied.
BuyAtMarket

PositionType Property

PositionType PositionType

Returns the type of Position, either long or short.  Possible values are:

  • PositionType.Long
  • PositionType.Short

Example

protected override void Execute(){
    for(int bar = 5; bar < Bars.Count; bar++)
    {
        if( IsLastPositionActive )
        {
            if( LastPosition.PositionType == PositionType.Long ) 
                SellAtStop( bar+1, LastPosition, Lowest.Series( Low, 4 )[bar-1] ); else
                CoverAtStop( bar+1, LastPosition, Highest.Series( High, 4 )[bar-1] );
        } else
        {
            if( BuyAtStop( bar+1, Highest.Series( High, 4 )[bar-1] ) == null )
                ShortAtStop( bar+1, Lowest.Series( Low, 4 )[bar-1] );
        }
    }
}
BuyAtMarket

Priority Property

double Priority

The Priority property comes in to play if there is a situation where there are several trade alerts generated in a simulation, but there is only enough capital to take some of the alerts.  In this case, the trades are executed in order of the Positions' Priority value, with the higher numeric values taking precedence.

Priority is generally used for Strategies that use Buy/ShortAtMarket (or AtClose) entries. For example, assume that your trading system generates 10 orders to place on the next bar, but you have cash enough for 4 orders only. Prior to placing orders, it's possible to determine which of the orders to place based on some indicator or price.

AtLimit/AtStop Entry Orders

Generally speaking, you should not assign Priority for Strategies that use AtLimit/AtStop entries. Doing so may create a peeking effect since it's often not possible to know which limit (or stop) orders will execute first when orders are placed for multiple instruments. You can, however, realistically use the inverse of the HHmm time-of-day as the correct Priority value. In other words, trades that occur earlier in the day should be assigned higher priority.

Exceptions:

  1. If the script employs a "multi-dip buyer" strategy, assign a higher Priority value to AtLimit orders with higher limit prices. If you don't, the possibility exists to execute orders with lower limit prices first (and vice-versa for ShortAtLimit).
  2. You can intentionally peek to determine if an AtLimit/AtStop order occurred at the opening price, and in this case you could assign an equally-high priority to these Positions. This is a valid use of peeking in backtesting.

Warning!

You should employ Priority in Strategies that use multiple order-entry types, such as AtMarket and AtLimit orders. Since the Strategy window does not distinguish between the types, set a higher priority for AtMarket entries so that they are processed before AtLimit/AtStop orders on the same bar.

Note

  • Positions are initialized a random Priority value.  This means that simulations run consecutively could generate different results if there is not enough capital to take all of the trades generated.

Example

protected override void Execute(){
    
    // Commodity Selection Index by Welles Wilder Jr. (c) 1979
    // Run this strategy in Futures mode on a symbol which has defined margin/point value

    int Commission = 8;
    int adxPeriod = 14;
    DataSeries CSI = new DataSeries( Bars, "Commodity Selection Index (CSI)" );
    SymbolInfo si = Bars.SymbolInfo;

    if( si.Margin > 0 )
    {
        for(int bar = adxPeriod; bar < Bars.Count; bar++)
        {
            CSI[bar] = ADXR.Series( Bars, adxPeriod )[bar] * ATR.Series( Bars, adxPeriod )[bar] * ( ( si.PointValue / Math.Sqrt( si.Margin ) ) * (float) 1 / ( 150 + Commission )  ) * 100;
        }
    } else
        // Will not execute if margin is not specified in Symbol Info Manager
        Abort();

    // Plot Commodity Selection Index on chart
    ChartPane CSIPane = CreatePane( 40, true, true );
    PlotSeries( CSIPane, CSI, Color.Blue, WealthLab.LineStyle.Solid, 2 );
    
    PlotSeries( PricePane, Lowest.Series( Close, 10 ), Color.Blue, WealthLab.LineStyle.Solid, 1 );
    PlotSeries( PricePane, Highest.Series( Close, 10 ), Color.Red, WealthLab.LineStyle.Solid, 1 );

    for(int bar = 20; bar < Bars.Count; bar++)
    {
        if (IsLastPositionActive)
        {
            Position p = LastPosition;
            if ( p.PositionType == PositionType.Long )
            SellAtStop( bar+1, p, Lowest.Series( Close, 10 )[bar], "Exit" );
            if ( p.PositionType == PositionType.Short )
            CoverAtStop( bar+1, p, Highest.Series( Close, 10 )[bar], "Cover" );
        }
        else
        {
            if ( Close[bar] > Highest.Series( Close, 20 )[bar-1] )
                if ( BuyAtMarket( bar+1, Convert.ToString( CSI[bar] ) ) != null )
                    LastActivePosition.Priority = CSI[bar];

            if ( Close[bar] < Lowest.Series( Close, 20 )[bar-1] )
                if ( ShortAtMarket( bar+1, Convert.ToString( CSI[bar] ) ) != null )
                    LastActivePosition.Priority = CSI[bar];
        }
    }
}
BuyAtMarket

ProfitPerBar Property

double ProfitPerBar

Returns the net profit of the Position divided by the number of bars held.  If the Position is still active, the number of bars is based onthe total number of bars held as of the last bar of the chart.  The ProfitPerBar property is primarily intended for use by Performance Visualizers, not Strategies.

Remarks

  • In portfolio simulation mode, all trades are pre-executed using 1 share per Position, and then position sizing is applied after the fact.  So the ProfitPerBar property will always be based on 1 share while the Strategy is executing.
  • The ProfitPerBar property is always available to Performance Visualizers, which execute after the position sizing has been applied.

 

BuyAtMarket

RiskStopLevel Property

double RiskStopLevel

Specifies the initial stop level (price) of the Position. This stop level is used when you select the Maximum Risk Pct Position Sizing option.  This option specifies the maximum amount of capital you are willing to risk on each trade.  When this option is selected, you must set the value of RiskStopLevel in your Strategy code to indicate the initial stop loss value for a newly created Position.

For example, a simple channel breakout system might enter at the highest 20 bar high, and exit at the lowest 20 bar low.  Prior to issuing the BuyAtMarket, or BuyAtStop, you should set RiskStopLevel to the lowest Low value of the past 20 bars as the initial stop level for the long Position.

Remarks

  • If you select the Maximum Risk Pct position sizing option and do not set RiskStopLevel in your Strategy code, you will receive an error message when attempting to run the Strategy.
  • You must also be diligent in your Strategy to actually use the established stop level as an exit.  If you do not, the Strategy could lose considerably more than the Maximum Risk that you established in the Position Size setting.
  • Once a RiskStopLevel is established for a Position, do not change it. The [last] value assigned to a Position's RiskStopLevel is used to determine % Risk sizing, consequently reassigning its value after the Position is established is effectively a peeking error.

Example

protected override void Execute(){    ClearDebug();
    for(int bar = 3; bar < Bars.Count; bar++)
    {                
        if( CumUp.Value(bar, Close, 1) >= 2 )
        {
            for(int pos = ActivePositions.Count - 1; pos >= 0; pos--)
            {
                Position p = ActivePositions[pos];
                PrintDebug(bar + "\t" + p.EntryBar  + "\t" + p.RiskStopLevel.ToString("0.00") + "\t" + p.EntrySignal );
                SellAtMarket(bar + 1, p);           
            }
            PrintDebug("");
        }                

        RiskStopLevel = Close[bar] - 1.0;
        if( CumDown.Value(bar, Close, 1) >= 2 )
            BuyAtMarket(bar + 1, Convert.ToString(RiskStopLevel) );
    }
}
BuyAtMarket

Shares Property

double Shares

Returns the number of shares (or contracts) that the Position contains.

Remarks

  • In portfolio simulation mode, all trades are pre-executed using 1 share per Position, and then position sizing is applied after the fact.  So the Shares property will always return 1 while the Strategy is executing.
  • The Shares property is always available to Performance Visualizers, which execute after the position sizing has been applied.

Example

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using WealthLab;
using WealthLab.Indicators;

namespace WealthLab.Strategies
{
    public class MyStrategy : WealthScript
    {
        // Write number of shares of open Positions to debug window
        protected void WriteOpenTrades()
        {
            ClearDebug();
            // Cycle through open positions
            foreach( Position p in Positions )
                if ( p.Active )
                PrintDebug( p.Shares + " Shares " + p.Bars.Symbol );
        }

        protected override void Execute()
        {
            for(int bar = 20; bar < Bars.Count; bar++)
            {
                if (IsLastPositionActive)
                {
                    SellAtMarket( bar+1, LastPosition );
                }
                else
                {
                    BuyAtMarket( bar+1 );
                }
                
                // Try this in Raw Profit, with Fixed dollar
                WriteOpenTrades();
            }
        }
    }
}

BuyAtMarket

Size Property

double Size

Returns the dollar size of the Position.  For equities and mutual funds this is the shares multiplied by the entry price.  For futures, this is the contracts (Shares property) multiplied by the margin of the contract (Bars.Margin).

Remarks

  • In portfolio simulation mode, all trades are pre-executed using 1 share per Position, and then position sizing is applied after the fact.  So the Size property will always be based on 1 share while the Strategy is executing.
  • The Size property is always available to Performance Visualizers, which execute after the position sizing has been applied.

Example

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using WealthLab;
using WealthLab.Indicators;

namespace WealthLab.Strategies
{
    public class MyStrategy : WealthScript
    {
        // Write number of shares of open Positions to debug window
        protected void WriteOpenTrades()
        {
            ClearDebug();
            // Cycle through open positions
            foreach( Position p in Positions )
                if ( p.Active )
                    PrintDebug( "Position value in " + p.Bars.Symbol + ": " + String.Format( "{0:c}", p.Size ) );
        }

        protected override void Execute()
        {
            for(int bar = 20; bar < Bars.Count; bar++)
            {
                if (IsLastPositionActive)
                {
                    SellAtMarket( bar+1, LastPosition );
                }
                else
                {
                    BuyAtMarket( bar+1 );
                }
                
                // Run this in Raw Profit mode
                WriteOpenTrades();
            }
        }
    }
}

BuyAtMarket

Tag Property

object Tag

The Tag property allows you to store any object with a Position.


Example

protected override void Execute(){
    for(int bar = 10; bar < Bars.Count; bar++)
    {
        if( IsLastPositionActive )
        {
            SellAtLimit( bar+1, LastPosition, (double) LastPosition.Tag );
        } else
        {
            if( ( Close[bar] > Close[bar-3] ) & ( Close[bar-3] > Close[bar-5] ) )
                if( BuyAtMarket( bar+1 ) != null )
                    // Store target price in the position's tag property
                    LastPosition.Tag = (Close[bar]*1.05 );
        }
    }
}
BuyAtMarket

TrailingStop Property

double TrailingStop

Provides access to the most recent trailing stop value for the Position.  Trailing stop levels come from calling the SellAtTrailingStop or CoverAtTrailingStop WealthScript methods.  The trailing stop is adjusted upward if the most recently passed value is higher than the current stop level.


Example

protected override void Execute(){
    PlotStops();
    int period = 20;
    SMA sma = SMA.Series( Close, period );
    PlotSeries( PricePane, sma, Color.BurlyWood, WealthLab.LineStyle.Solid, 1 );
    
    for(int bar = 3*period; bar < Bars.Count; bar++)
    {
        if (IsLastPositionActive)
        {
            Position p = LastActivePosition;
            
            // Initiate a trailing stop after a 5% gain
            if ( p.MFEAsOfBarPercent( bar ) > 5 )
            {
                CoverAtTrailingStop( bar+1, p, sma[bar], "Trailing Stop" );
            }
            else
                CoverAtStop( bar+1, p, p.EntryPrice * 1.10, "10% Stop Loss" );
            
            if( ( bar == Bars.Count-1 ) & ( p.TrailingStop > 0 ) )
                DrawLabel( PricePane, "Current trailing stop value = " + p.TrailingStop.ToString(), Color.Indigo );
            
        }
        else
        {
            // sample entry rule
            ShortAtStop( bar+1, sma[bar]*0.97, "3% band around SMA" );
        }
    }
}